# move-auditor

Security audit of Move code (Sui / Aptos). Auto-detects platform. Trigger on "audit", "check this contract", "review for security". Modes - default (full repo) or a specific filename.

- **Kind:** skill
- **Source:** https://github.com/ZerodriftSec/move-skills
- **Page:** https://forefy.com/skills/1ac2aa0e-3686-4cdc-ab7e-a8ececd387bb
- **API (JSON + files):** https://forefy.com/api/asr/1ac2aa0e-3686-4cdc-ab7e-a8ececd387bb

---

## SKILL.md

---
name: move-auditor
description: Security audit of Move code (Sui / Aptos). Auto-detects platform. Trigger on "audit", "check this contract", "review for security". Modes - default (full repo) or a specific filename.
---

# Move Smart Contract Security Audit

You are the orchestrator of a parallelized Move security audit.

## Mode Selection

**Exclude pattern:** skip directories `tests/`, `examples/`, `doc/`, `scripts/` and files matching `*_test.move`, `*Test*.move` or `*Mock*.move`.

- **Default** (no arguments): scan all `.move` files using the exclude pattern. Use Bash `find` (not Glob).
- **`$filename ...`**: scan the specified file(s) only.

**Flags:**

- `--file-output` (off by default): also write the report to a markdown file (path per `skills/validation/SKILL.md`). Never write a report file unless explicitly passed.

## Orchestration Flow

### Turn 0 — Banner

Print the banner:

```bash
bash scripts/banner.sh
```

### Turn 1 — Detect Platform

Run the detection script:

```bash
python3 scripts/detect-platform.py <project_path>
```

This recursively scans for `Move.toml` files and checks their dependencies:
- `MystenLabs/sui.git` → `sui`
- `aptos-labs/aptos-core.git` → `aptos`

Store the result as `{platform}`.

### Turn 2 — Discover

Make these parallel tool calls in one message:

a. Bash `find` for in-scope `.move` files per mode selection and exclude pattern.
b. Read `skills/validation/SKILL.md`
c. Bash `mktemp -d /tmp/move-audit-XXXXXX` → store as `{bundle_dir}`

If no `.move` files found, print: `No Move source files found.` and stop.

### Turn 3 — Prepare

Build all bundles in a single Bash command using `cat`:

1. `{bundle_dir}/source.md` — ALL in-scope `.move` files, each with a `### path/to/file.move` header and fenced code block.

2. Agent bundles = `source.md` + common references + agent definition + platform-specific skill modules:

Every bundle includes the two common references:
- `skills/move-auditor/references/common/move-language.md`
- `skills/move-auditor/references/common/move-vulnerabilities.md`

**Common bundles (all platforms):**

| Bundle | Agent Definition | Appended skill modules (relative to `skills/move-auditor/references/{platform}/`) |
|--------|-----------------|---------------------------------------------------------------------|
| `agent-1-bundle.md` | `agents/ability-type-safety-agent.md` | `ability-analysis.md` + `type-safety.md` |
| `agent-3-bundle.md` | `agents/flash-loan-allocation-agent.md` | `flash-loan-interaction.md` + `share-allocation-fairness.md` |
| `agent-4-bundle.md` | `agents/token-flow-zero-state-agent.md` | `token-flow-tracing.md` + `zero-state-return.md` |
| `agent-5-bundle.md` | `agents/centralization-roles-agent.md` | `centralization-risk.md` + `semi-trusted-roles.md` |
| `agent-6-bundle.md` | `agents/oracle-staleness-agent.md` | `oracle-analysis.md` + `temporal-parameter-staleness.md` |
| `agent-8-bundle.md` | `agents/migration-crosschain-agent.md` | `migration-analysis.md` + `cross-chain-timing.md` |

**Platform-specific bundles:**

| Bundle | Platform | Agent Definition | Appended skill modules (relative to `skills/move-auditor/references/`) |
|--------|----------|-----------------|----------------------------------------------------------------------|
| `agent-2-bundle.md` | **Sui** | `agents/ownership-composability-agent.md` | `sui/object-ownership.md` + `sui/ptb-composability.md` |
| `agent-2-bundle.md` | **Aptos** | `agents/ownership-composability-agent.md` | `aptos/reentrancy-analysis.md` + `aptos/ref-lifecycle.md` |
| `agent-7-bundle.md` | **Sui** | `agents/dependency-ecosystem-agent.md` | `sui/dependency-audit.md` + `sui/package-version-safety.md` |
| `agent-7-bundle.md` | **Aptos** | `agents/dependency-ecosystem-agent.md` | `aptos/dependency-audit.md` + `aptos/fungible-asset-security.md` |

```
cat source.md references/common/move-language.md references/common/move-vulnerabilities.md references/{platform}/CORE_VULNERABILITIES.md references/{platform}/{platform-vuln-file}.md agents/{agent-file}.md references/{platform}/{skill-1}.md references/{platform}/{skill-2}.md agents/shared-rules.md > agent-N-bundle.md
```

- `{platform-vuln-file}` = `SUI_VULNERABILITIES.md` for Sui, `APTOS_VULNERABILITIES.md` for Aptos.

Append `agents/shared-rules.md` to every bundle.

Print line counts for every bundle and `source.md`. Do NOT inline file content into agent prompts.

### Turn 4 — Run Specialists

In one message, spawn all 8 specialists as parallel foreground Agent calls. Prompt template:

```
Your bundle file is {bundle_dir}/agent-N-bundle.md (XXXX lines).
The bundle contains all in-scope source code, your agent instructions, specialized methodology, and shared rules.
Read the bundle fully before producing findings.
Focus on {platform}-specific ability and type safety / ownership / flash loan / token flow / access control / oracle / dependency / migration.
```

Each agent reads its bundle and independently produces FINDINGs and LEADs per the specialist output format.

### Turn 5 — Depth Analysis

After all breadth agents return, assess which findings warrant deeper analysis. For each breadth finding that meets depth trigger criteria:

| Depth Agent | Trigger |
|-------------|---------|
| `depth-token-flow-agent` | Token balance, transfer, withdrawal, accounting patterns |
| `depth-state-trace-agent` | Multi-function state mutation, constraint violations |
| `depth-edge-case-agent` | Boundary conditions, zero-state, dust, first/last participant |
| `depth-external-agent` | External calls, cross-chain, oracle dependencies, MEV |

Spawn relevant depth agents in parallel. Each receives source + specific findings + agent definition from `agents/`.

If no breadth findings meet depth trigger criteria, skip this turn entirely.

### Turn 6 — Deduplicate, Validate & Report

Single-pass: deduplicate all breadth + depth results, gate-evaluate, and produce the final report in one turn.

#### 1. Deduplicate

Parse every FINDING and LEAD from all agents. Group by `group_key` field (format: `Module | function | bug-class`). Exact-match first; then merge synonymous bug_class tags. Keep best version per group, number sequentially, annotate `[agents: N]`.

#### 2. Gate Evaluation

Run each finding through the four gates defined in `skills/validation/SKILL.md`.

#### 3. Confidence Scoring

Apply confidence scoring per `skills/validation/SKILL.md`.

#### 4. Lead Promotion

- Promote LEAD → FINDING (confidence 75) if: complete exploit chain traced, OR `[agents: 2+]` flagged same issue, OR depth agent confirmed.
- No deployer-intent reasoning — evaluate what the code _allows_.

#### 5. Fix Verification (confidence >= 80 only)

Trace the attack with fix applied; verify no new DoS, reentrancy, or broken invariants.

#### 6. Format and Print

Format per `skills/validation/SKILL.md`. Exclude rejected items. If `--file-output`: also write to file.

## Vulnerability Categories

### Sui-Specific (S1–S10)

| ID | Category | Severity | Description |
|----|----------|----------|-------------|
| S1 | Object Ownership Bypass | CRITICAL | Unauthorized object transfer via public_transfer |
| S2 | Shared Object Manipulation | CRITICAL | Race conditions in shared objects |
| S3 | PTB Composition Attacks | HIGH | Malicious transaction block composition |
| S4 | Kiosk Exploitation | HIGH | Bypass kiosk rules/policies |
| S5 | Dynamic Field Abuse | HIGH | Unauthorized field access/modification |
| S6 | Transfer Policy Bypass | HIGH | Circumventing transfer restrictions |
| S7 | Capability Leakage | HIGH | AdminCap/OwnerCap transferred to unauthorized parties |
| S8 | Witness Pattern Abuse | CRITICAL | Improper one-time witness validation |
| S9 | Improper Abilities | CRITICAL | copy/drop on asset types |
| S10 | Upgrade Cap Mishandling | HIGH | Package upgrade authorization issues |

### Aptos-Specific (A1–A10)

| ID | Category | Severity | Description |
|----|----------|----------|-------------|
| A1 | Signer Validation Bypass | CRITICAL | Missing signer checks in privileged functions |
| A2 | Account Resource Abuse | HIGH | Unauthorized move_to/borrow_global access |
| A3 | Event Handle Manipulation | MEDIUM | Missing or forged event emissions |
| A4 | FungibleAsset Vulnerabilities | HIGH | Improper FA handling, Ref leakage |
| A5 | Table/SmartVector Issues | MEDIUM | Unbounded storage, DoS vectors |
| A6 | Multi-Signature/Auth Key | MEDIUM | Auth key rotation, replay attacks |
| A7 | Capability Leakage | HIGH | SignerCapability transfer issues |
| A8 | Witness Pattern Abuse | CRITICAL | Improper witness validation |
| A9 | Improper Abilities | CRITICAL | copy/drop on asset types |
| A10 | Reentrancy (Move 2.2+) | HIGH | Dynamic dispatch, FA hooks |

## Detection Commands

### Sui

```bash
# Find object definitions and transfers
rg "public struct.*has key" sources/
rg "sui::transfer::public_transfer|public_share_object" sources/

# Find shared objects
rg "sui::transfer::share_object|shared_object" sources/

# Find kiosk operations
rg "sui::kiosk" sources/

# Find dynamic fields
rg "sui::dynamic_field|dynamic_object_field" sources/

# Find capabilities
rg "AdminCap|OwnerCap|UpgradeCap" sources/

# Find witness patterns
rg "Witness|witness|has drop" sources/
```

### Aptos

```bash
# Find signer usage
rg "signer|signer::address_of" sources/

# Find entry functions
rg "public entry fun|entry fun" sources/

# Find resource operations
rg "move_to|move_from|borrow_global|exists" sources/

# Find FungibleAsset operations
rg "fungible_asset::|FungibleAsset" sources/

# Find event emissions
rg "event::emit|emit_event" sources/

# Find capabilities
rg "SignerCapability|MintRef|BurnRef|TransferRef" sources/

# Find witness patterns
rg "Witness|witness|has drop" sources/
```

## Skill Modules Reference

The following specialized skill modules are available. Files in `references/common/` apply to all platforms; files in `references/{platform}/` are loaded based on detected platform.

### Common (always loaded)

| Module | Location | Purpose |
|--------|----------|---------|
| move-language | `references/common/` | Comprehensive Move language reference |
| move-vulnerabilities | `references/common/` | Cross-platform Move vulnerability catalog (M1–M8) |

### Per-Platform Modules (`references/{platform}/`)

| Module | Trigger | Purpose |
|--------|---------|---------|
| CORE_VULNERABILITIES | Always | 8 core Move vulnerabilities with vulnerable/secure code |
| {PLATFORM}_VULNERABILITIES | Always | Platform-specific vulnerability categories |
| ability-analysis | Always | Analyze struct abilities (copy/drop/key/store) |
| attack-vectors | Always | Attack vector catalog with detection patterns |
| bit-shift-safety | Always | Check shift operations for DoS |
| centralization-risk | Capabilities detected | Analyze privilege concentration |
| cross-chain-timing | Bridge patterns | Cross-chain message validation |
| dependency-audit | External deps | Third-party dependency audit |
| economic-design-audit | Monetary params | Economic parameter analysis |
| external-precondition-audit | External calls | External module precondition analysis |
| flash-loan-interaction | Flash loan patterns | Flash loan attack surface |
| fork-ancestry | Recon phase | Known fork vulnerability patterns |
| migration-analysis | Upgrade patterns | Package upgrade / migration safety |
| oracle-analysis | Oracle usage | Oracle staleness/manipulation |
| semi-trusted-roles | Keeper/operator roles | Role-based attack vectors |
| share-allocation-fairness | Share minting | Allocation fairness analysis |
| temporal-parameter-staleness | Multi-step ops | Cached parameter staleness |
| token-flow-tracing | Balance operations | Token flow accounting |
| type-safety | Generics usage | Generic type constraints |
| verification-protocol | Verification phase | Move test verification |
| zero-state-return | First depositor | Zero state edge cases |

### Sui-Only Modules (`references/sui/`)

| Module | Trigger | Purpose |
|--------|---------|---------|
| object-ownership | Always | Object lifecycle audit |
| ptb-composability | Always (Sui) | PTB atomic composition risks |
| package-version-safety | UpgradeCap | Package upgrade risks |

### Aptos-Only Modules (`references/aptos/`)

| Module | Trigger | Purpose |
|--------|---------|---------|
| reentrancy-analysis | Dynamic dispatch | Move 2.2+ reentrancy vectors |
| ref-lifecycle | Ref types | Object Ref lifecycle audit |
| fungible-asset-security | FA patterns | FungibleAsset standard audit |

## references

```

```

## references/aptos

```

```

## references/aptos/APTOS_VULNERABILITIES.md

# Aptos-Specific Vulnerabilities

This document details vulnerabilities specific to the Aptos blockchain and its Move implementation.

---

## A1. Signer Validation Bypass

### Description
Missing or improper signer validation allows unauthorized access to account resources and privileged operations.

### Severity
CRITICAL

### Detection Pattern

```bash
# Find signer usage
rg "signer|signer::address_of" sources/
rg "public entry fun.*signer" sources/

# Find missing signer checks
rg "borrow_global_mut.*address" sources/
```

### Vulnerable Code

```move
module vulnerable::wallet {
    struct Wallet has key {
        balance: u64,
    }

    // BAD: No signer validation - anyone can withdraw from any wallet
    public entry fun withdraw(
        account: &signer,
        amount: u64,
        recipient: address
    ) acquires Wallet {
        let addr = signer::address_of(account);
        let wallet = borrow_global_mut<Wallet>(addr);

        // But addr could be anyone - no validation that account owns this wallet!
        wallet.balance = wallet.balance - amount;

        let recipient_wallet = borrow_global_mut<Wallet>(recipient);
        recipient_wallet.balance = recipient_wallet.balance + amount;
    }

    // BAD: Admin function without proper authorization
    public entry fun set_admin(
        admin: &signer,
        config: &mut Config
    ) {
        // No check if admin is actually authorized!
        config.admin = signer::address_of(admin);
    }
}
```

### Attack Scenario

1. Attacker calls `withdraw` with their own signer
2. Function doesn't validate that signer owns the wallet being modified
3. Attacker drains funds from any wallet

### Secure Code

```move
module secure::wallet {
    struct Wallet has key {
        balance: u64,
    }

    struct AdminCapability has key, store {}

    // GOOD: Proper signer validation
    public entry fun withdraw(
        account: &signer,
        amount: u64
    ) acquires Wallet {
        let addr = signer::address_of(account);
        assert!(exists<Wallet>(addr), EWalletNotFound);

        let wallet = borrow_global_mut<Wallet>(addr);
        assert!(wallet.balance >= amount, EInsufficientBalance);

        wallet.balance = wallet.balance - amount;
    }

    // GOOD: Transfer between own wallets
    public entry fun transfer(
        from: &signer,
        to: address,
        amount: u64
    ) acquires Wallet {
        let from_addr = signer::address_of(from);

        let from_wallet = borrow_global_mut<Wallet>(from_addr);
        assert!(from_wallet.balance >= amount, EInsufficientBalance);

        let to_wallet = borrow_global_mut<Wallet>(to);
        from_wallet.balance = from_wallet.balance - amount;
        to_wallet.balance = to_wallet.balance + amount;
    }

    // GOOD: Admin authorization via capability
    public entry fun set_admin(
        admin: &signer,
        config: &mut Config
    ) acquires AdminCapability {
        let admin_addr = signer::address_of(admin);
        assert!(
            exists<AdminCapability>(admin_addr),
            ENotAuthorized
        );
        config.admin = admin_addr;
    }
}
```

---

## A2. Account Resource Abuse

### Description
Aptos allows storing resources under any account. Improper access control can allow unauthorized resource manipulation.

### Severity
HIGH

### Detection Pattern

```bash
# Find move_to operations
rg "move_to|move_from" sources/
rg "borrow_global" sources/
```

### Vulnerable Code

```move
module vulnerable::game {
    struct PlayerData has key {
        score: u64,
        items: vector<Item>,
    }

    // BAD: Anyone can initialize player data for any address
    public entry fun init_player(
        account: &signer,
        target: address  // Could be anyone
    ) {
        move_to<PlayerData>(account, PlayerData {
            score: 0,
            items: vector::empty(),
        });
    }

    // BAD: Resource stored under wrong account
    struct GameConfig has key {
        admin: address,
        paused: bool,
    }

    public entry fun init_config(admin: &signer) {
        // Config should be under module address, not admin
        move_to<GameConfig>(admin, GameConfig {
            admin: signer::address_of(admin),
            paused: false,
        });
    }
}
```

### Secure Code

```move
module secure::game {
    struct PlayerData has key {
        score: u64,
        items: vector<Item>,
    }

    struct GameConfig has key {
        admin: address,
        paused: bool,
    }

    // GOOD: Player initializes their own data
    public entry fun init_player(account: &signer) {
        let addr = signer::address_of(account);
        assert!(!exists<PlayerData>(addr), EAlreadyInitialized);

        move_to<PlayerData>(account, PlayerData {
            score: 0,
            items: vector::empty(),
        });
    }

    // GOOD: Config under signer's address, validated
    public entry fun init_config(admin: &signer) {
        let addr = signer::address_of(admin);
        assert!(!exists<GameConfig>(addr), EAlreadyInitialized);

        move_to<GameConfig>(admin, GameConfig {
            admin: addr,
            paused: false,
        });
    }

    // GOOD: Only admin can modify config
    public entry fun set_paused(
        admin: &signer,
        paused: bool
    ) acquires GameConfig {
        let addr = signer::address_of(admin);
        let config = borrow_global_mut<GameConfig>(addr);

        assert!(config.admin == addr, ENotAuthorized);
        config.paused = paused;
    }
}
```

---

## A3. Event Handle Manipulation

### Description
Missing or forged events that affect off-chain monitoring and indexing.

### Severity
MEDIUM

### Detection Pattern

```bash
# Find event usage
rg "event::emit|emit_event" sources/
rg "EventHandle" sources/
```

### Vulnerable Code

```move
module vulnerable::token {
    struct TransferEvent has drop, store {
        from: address,
        to: address,
        amount: u64,
    }

    // BAD: No event for critical transfer
    public entry fun transfer(
        from: &signer,
        to: address,
        amount: u64
    ) acquires Balance {
        let from_addr = signer::address_of(from);

        let from_balance = borrow_global_mut<Balance>(from_addr);
        let to_balance = borrow_global_mut<Balance>(to);

        from_balance.value = from_balance.value - amount;
        to_balance.value = to_balance.value + amount;

        // No event emitted!
    }

    // BAD: Incorrect event data
    public entry fun transfer_with_event(
        from: &signer,
        to: address,
        amount: u64
    ) acquires Balance, EventStore {
        // ... transfer logic ...

        let event_store = borrow_global_mut<EventStore>(
            @vulnerable::token
        );
        event::emit_event(&mut event_store.transfer_events, TransferEvent {
            from: to,      // WRONG: swapped
            to: from_addr, // WRONG: swapped
            amount: 0,     // WRONG: hidden amount
        });
    }
}
```

### Secure Code

```move
module secure::token {
    use aptos_std::event::{Self, EventHandle};

    struct TransferEvent has drop, store {
        from: address,
        to: address,
        amount: u64,
        timestamp: u64,
    }

    struct EventStore has key {
        transfer_events: EventHandle<TransferEvent>,
    }

    // GOOD: Emit correct event for all transfers
    public entry fun transfer(
        from: &signer,
        to: address,
        amount: u64
    ) acquires Balance, EventStore {
        let from_addr = signer::address_of(from);

        let from_balance = borrow_global_mut<Balance>(from_addr);
        let to_balance = borrow_global_mut<Balance>(to);

        assert!(from_balance.value >= amount, EInsufficientBalance);

        from_balance.value = from_balance.value - amount;
        to_balance.value = to_balance.value + amount;

        // Emit correct event
        let event_store = borrow_global_mut<EventStore>(@secure::token);
        event::emit_event(&mut event_store.transfer_events, TransferEvent {
            from: from_addr,
            to: to,
            amount: amount,
            timestamp: timestamp::now_seconds(),
        });
    }
}
```

---

## A4. Coin/FungibleAsset Vulnerabilities

### Description
Improper handling of Aptos Coin and FungibleAsset types leading to loss of funds or unauthorized minting.

### Severity
HIGH

### Detection Pattern

```bash
# Find coin operations
rg "coin::|aptos_framework::coin" sources/
rg "fungible_asset::" sources/
rg "mint|burn|withdraw|deposit" sources/
```

### Vulnerable Code

```move
module vulnerable::token {
    use aptos_framework::coin::{Self, Coin};

    // BAD: Unchecked mint
    public entry fun mint_tokens(
        account: &signer,
        amount: u64,
        recipient: address
    ) acquires MintCapabilityStore {
        let minter = signer::address_of(account);
        let cap_store = borrow_global<MintCapabilityStore>(minter);

        // No amount limit check!
        let coins = coin::mint(amount, &cap_store.mint_cap);

        coin::deposit(recipient, coins);
    }

    // BAD: Withdraw from wrong account
    public entry fun withdraw_to(
        account: &signer,
        from: address,  // Not validated against account
        amount: u64
    ): Coin<TOKEN> {
        let _ = signer::address_of(account); // Unused!
        coin::withdraw<TOKEN>(from, amount)
    }
}
```

### Attack Scenario

1. Attacker calls `mint_tokens` with large amount
2. No limit check, unlimited tokens minted
3. Attacker drains liquidity pools

### Secure Code

```move
module secure::token {
    use aptos_framework::coin::{Self, Coin};
    use aptos_framework::fungible_asset::{Self, FungibleAsset, MintRef, BurnRef};

    struct MintLimits has key {
        daily_limit: u64,
        minted_today: u64,
        last_reset: u64,
    }

    // GOOD: Rate-limited minting
    public entry fun mint_tokens(
        account: &signer,
        amount: u64,
        recipient: address
    ) acquires MintCapabilityStore, MintLimits {
        let minter = signer::address_of(account);
        assert!(exists<MintCapabilityStore>(minter), ENotAuthorized);

        // Check rate limits
        let limits = borrow_global_mut<MintLimits>(@secure::token);
        let now = timestamp::now_seconds();

        if (now - limits.last_reset >= 86400) {
            limits.minted_today = 0;
            limits.last_reset = now;
        };

        assert!(
            limits.minted_today + amount <= limits.daily_limit,
            EExceedsLimit
        );
        limits.minted_today = limits.minted_today + amount;

        let cap_store = borrow_global<MintCapabilityStore>(minter);
        let coins = coin::mint(amount, &cap_store.mint_cap);

        coin::deposit(recipient, coins);
    }

    // GOOD: Only withdraw from own account
    public entry fun withdraw(
        account: &signer,
        amount: u64
    ): Coin<TOKEN> {
        let addr = signer::address_of(account);
        coin::withdraw<TOKEN>(addr, amount)
    }

    // GOOD: FungibleAsset pattern (Aptos standard)
    public entry fun mint_fa(
        account: &signer,
        ref: &MintRef,
        amount: u64,
        recipient: address
    ) {
        assert!(amount <= MAX_MINT_AMOUNT, EExceedsLimit);

        let fa = fungible_asset::mint(ref, amount);
        fungible_asset::deposit(recipient, fa);
    }
}
```

---

## A5. Table and Smart Vector Issues

### Description
Improper use of Aptos Table and SmartVector leading to DoS or state manipulation.

### Severity
MEDIUM

### Detection Pattern

```bash
# Find table usage
rg "table::|aptos_std::table" sources/
rg "smart_vector::" sources/
```

### Vulnerable Code

```move
module vulnerable::registry {
    use aptos_std::table::{Self, Table};

    struct Registry has key {
        entries: Table<address, Entry>,
    }

    // BAD: No size limit - can grow unbounded
    public entry fun add_entry(
        account: &signer,
        registry: &mut Registry
    ) {
        let addr = signer::address_of(account);
        table::add(&mut registry.entries, addr, Entry {
            data: vector::empty(),
        });
    }

    // BAD: Table can be spammed, causing DoS
    public entry fun iterate_all(
        registry: &Registry
    ) {
        // Iteration becomes slow with many entries
        let len = table::length(&registry.entries);
        // ... slow iteration
    }
}
```

### Secure Code

```move
module secure::registry {
    use aptos_std::table::{Self, Table};
    use aptos_std::smart_vector::{Self, SmartVector};

    const MAX_ENTRIES: u64 = 10000;

    struct Registry has key {
        entries: Table<address, Entry>,
        entry_count: u64,
    }

    // GOOD: Size-limited registry
    public entry fun add_entry(
        account: &signer,
        registry: &mut Registry
    ) acquires Registry {
        let addr = signer::address_of(account);

        assert!(
            registry.entry_count < MAX_ENTRIES,
            ERegistryFull
        );

        assert!(
            !table::contains(&registry.entries, addr),
            EAlreadyExists
        );

        table::add(&mut registry.entries, addr, Entry {
            data: vector::empty(),
        });

        registry.entry_count = registry.entry_count + 1;
    }

    // GOOD: Use SmartVector for bounded collections
    struct BoundedRegistry has key {
        entries: SmartVector<address, Entry>,
    }

    public entry fun add_bounded(
        account: &signer,
        registry: &mut BoundedRegistry
    ) {
        let addr = signer::address_of(account);

        smart_vector::push_back(
            &mut registry.entries,
            addr,
            Entry { data: vector::empty() }
        );
    }
}
```

---

## A6. Multi-Signature and Auth Key Issues

### Description
Improper handling of Aptos authentication keys and multi-signature schemes.

### Severity
MEDIUM

### Detection Pattern

```bash
# Find multisig patterns
rg "multisig|MultiSig" sources/
rg "authentication_key" sources/
```

### Vulnerable Code

```move
module vulnerable::multisig {
    struct Wallet has key {
        owners: vector<address>,
        threshold: u64,
        pending_tx: vector<PendingTx>,
    }

    // BAD: Threshold can be changed without proper validation
    public entry fun change_threshold(
        signer: &signer,
        wallet: &mut Wallet,
        new_threshold: u64
    ) {
        // No validation that signer is an owner!
        wallet.threshold = new_threshold;
    }

    // BAD: Replay attack possible
    public entry fun execute_tx(
        wallet: &mut Wallet,
        tx_hash: vector<u8>,
        signatures: vector<Signature>
    ) {
        // No nonce/check to prevent replay
        assert!(
            verify_signatures(wallet, tx_hash, signatures),
            EInvalidSignatures
        );
        execute(wallet, tx_hash);
    }
}
```

### Secure Code

```move
module secure::multisig {
    struct Wallet has key {
        owners: vector<address>,
        threshold: u64,
        nonce: u64,  // GOOD: Replay protection
        pending_tx: Table<u64, PendingTx>,
    }

    // GOOD: Only owners can change threshold
    public entry fun change_threshold(
        signer: &signer,
        wallet_addr: address,
        new_threshold: u64
    ) acquires Wallet {
        let signer_addr = signer::address_of(signer);
        let wallet = borrow_global_mut<Wallet>(wallet_addr);

        // Verify signer is an owner
        assert!(is_owner(wallet, signer_addr), ENotOwner);

        // Validate new threshold
        assert!(new_threshold > 0, EInvalidThreshold);
        assert!(
            new_threshold <= vector::length(&wallet.owners),
            EThresholdTooHigh
        );

        wallet.threshold = new_threshold;
    }

    // GOOD: Nonce-based replay protection
    public entry fun execute_tx(
        wallet_addr: address,
        tx: PendingTx,
        signatures: vector<Signature>
    ) acquires Wallet {
        let wallet = borrow_global_mut<Wallet>(wallet_addr);

        // Include nonce in hash
        let tx_hash = hash_tx(wallet.nonce, &tx);

        assert!(
            verify_signatures(&wallet.owners, wallet.threshold, tx_hash, signatures),
            EInvalidSignatures
        );

        // Increment nonce
        wallet.nonce = wallet.nonce + 1;

        execute(&mut wallet, tx);
    }

    fun is_owner(wallet: &Wallet, addr: address): bool {
        let i = 0;
        let len = vector::length(&wallet.owners);
        while (i < len) {
            if (*vector::borrow(&wallet.owners, i) == addr) {
                return true
            };
            i = i + 1;
        };
        false
    }
}
```

---

## Aptos Security Best Practices

### 1. Signer Validation Pattern

```move
// Always validate signer for privileged operations
public entry fun privileged_op(signer: &signer) {
    let addr = signer::address_of(signer);
    assert!(is_authorized(addr), ENotAuthorized);
    // ... operation
}
```

### 2. Resource Storage Pattern

```move
// Store resources under appropriate addresses
// - User data: under user's address
// - Global config: under module/deployer address
struct GlobalConfig has key { ... }

// Initialize under deployer
fun init_module(admin: &signer) {
    move_to<GlobalConfig>(admin, GlobalConfig { ... });
}
```

### 3. Event Emission Pattern

```move
// Emit events for all state changes
struct TransferEvent has drop, store { ... }

public entry fun transfer(...) acquires EventStore {
    // ... state change ...

    event::emit_event(&mut event_store.events, TransferEvent {
        // accurate data
    });
}
```

### 4. Coin/FA Pattern

```move
// Use FungibleAsset for new tokens
// Use rate limiting for minting
// Always validate withdraw addresses
```

### 5. Testing Pattern

```move
#[test(admin = @0x1, user = @0x2)]
fun test_access_control(admin: signer, user: signer) {
    // Test admin can access
    privileged_op(&admin);

    // Test user cannot access
    assert!(fails_with(ENotAuthorized, || {
        privileged_op(&user);
    }), 0);
}
```

---

## Summary Checklist

| Check | Description |
|-------|-------------|
| Signer Validation | All privileged ops verify signer |
| Resource Storage | Resources under correct addresses |
| Events | All state changes emit events |
| Coin/FA Operations | Rate-limited, authorized minting |
| Table/Vector | Bounded sizes, DoS protection |
| Multi-sig | Nonce-based replay protection |

## references/aptos/CORE_VULNERABILITIES.md

#  Core Move Vulnerabilities

This document details core Move language vulnerabilities that apply across all Move-based blockchains.

---

## 1. Improper Resource Abilities

### Description
Move's ability system (`copy`, `drop`, `key`, `store`) controls how structs behave. Incorrect abilities on asset types can lead to duplication or loss of funds.

### Severity
CRITICAL

### Detection Pattern

```bash
# Find structs representing assets/value
rg "public struct.*Coin|public struct.*Token|public struct.*Asset" sources/
rg "has.*(copy|drop)" sources/
```

### Vulnerable Code

```move
// BAD: Coin can be duplicated
public struct Coin has key, store, copy {
    value: u64,
}

// BAD: Coin can be silently dropped (lost)
public struct Coin has key, store, drop {
    value: u64,
}

// BAD: Both issues combined
public struct Token has key, store, copy, drop {
    amount: u64,
}
```

### Attack Scenario

1. **Copy Attack**: User creates a coin, copies it, and spends both copies
2. **Drop Attack**: User receives payment, but coins are accidentally dropped, losing value

### Secure Code

```move
// GOOD: Asset without copy or drop - must be explicitly handled
public struct Coin has key, store {
    value: u64,
}

// For burning, create explicit function
public entry fun burn(coin: Coin, _ctx: &mut TxContext) {
    let Coin { value: _ } = coin;
    // Coin is consumed, value is burned
}
```

### Testing

```move
#[test]
#[expected_failure]
fun test_cannot_copy_coin() {
    let coin = Coin { value: 100 };
    let copy = coin; // This should fail to compile if copy is not allowed
}
```

---

## 2. Missing Access Control

### Description
Public or entry functions without proper authorization checks allow unauthorized operations.

### Severity
CRITICAL

### Detection Pattern

```bash
# Find entry and public functions
rg "public entry fun|public fun" sources/

# Check for capability/signer parameters
rg "public entry fun.*\(" sources/ | grep -v "Cap\|signer"
```

### Vulnerable Code

```move
module vulnerable::admin {
    public struct AdminCap has key { id: UID }
    public struct Config has key {
        id: UID,
        fee_rate: u64,
        paused: bool,
    }

    // BAD: Anyone can change fee rate
    public entry fun set_fee_rate(
        config: &mut Config,
        new_rate: u64,
        _ctx: &mut TxContext
    ) {
        config.fee_rate = new_rate;
    }

    // BAD: Anyone can pause the contract
    public entry fun emergency_pause(
        config: &mut Config,
        _ctx: &mut TxContext
    ) {
        config.paused = true;
    }

    // BAD: Anyone can mint tokens
    public entry fun mint(
        treasury: &mut Treasury,
        amount: u64,
        recipient: address,
        ctx: &mut TxContext
    ) {
        transfer::public_transfer(
            Coin { id: object::new(ctx), value: amount },
            recipient
        );
    }
}
```

### Attack Scenario

1. Attacker identifies unprotected `set_fee_rate` function
2. Attacker sets fee rate to 0
3. Attacker uses protocol without fees
4. Protocol loses all fee revenue

### Secure Code

```move
module secure::admin {
    public struct AdminCap has key { id: UID }
    public struct Config has key {
        id: UID,
        fee_rate: u64,
        paused: bool,
    }

    // GOOD: Requires admin capability
    public entry fun set_fee_rate(
        _: &AdminCap,  // Capability check
        config: &mut Config,
        new_rate: u64,
        _ctx: &mut TxContext
    ) {
        assert!(new_rate <= 10000, EInvalidRate); // Max 100%
        config.fee_rate = new_rate;
    }

    // GOOD: Requires admin capability
    public entry fun emergency_pause(
        _: &AdminCap,
        config: &mut Config,
        _ctx: &mut TxContext
    ) {
        config.paused = true;
    }

    // GOOD: Requires minter capability
    public entry fun mint(
        _: &MinterCap,
        treasury: &mut Treasury,
        amount: u64,
        recipient: address,
        ctx: &mut TxContext
    ) {
        assert!(!treasury.paused, EPaused);
        assert!(amount <= treasury.max_mint, EExceedsLimit);
        transfer::public_transfer(
            treasury::withdraw(treasury, amount),
            recipient
        );
    }
}
```

### Testing

```move
#[test_only]
module secure::admin_tests {
    use secure::admin;

    #[test]
    #[expected_failure(abort_code = admin::ENotAuthorized)]
    fun test_unauthorized_fee_change() {
        // Create config but no capability
        let config = admin::create_test_config();
        admin::set_fee_rate(&mut config, 500); // Should fail
    }

    #[test]
    fun test_authorized_fee_change() {
        let (cap, config) = admin::create_test_setup();
        admin::set_fee_rate(&cap, &mut config, 500);
        assert!(config.fee_rate == 500, 0);
    }
}
```

---

## 3. Witness Pattern Abuse

### Description
Witness pattern used incorrectly, allowing unauthorized type creation or token minting.

### Severity
CRITICAL

### Detection Pattern

```bash
# Find witness-related patterns
rg "Witness|witness" sources/
rg "public struct.*has drop" sources/
rg "ensure!|assert!.*witness" sources/
```

### Vulnerable Code

```move
module vulnerable::token {
    // BAD: Witness can be created anywhere
    public struct Witness has drop {}

    public fun create_collection(_: Witness, ctx: &mut TxContext) {
        // Create collection
    }

    public entry fun create_token(
        _: Witness,
        name: String,
        ctx: &mut TxContext
    ) {
        // Anyone can create this witness and call the function
        let witness = Witness {};
        create_collection(witness, ctx);
    }
}

// BAD: Witness with wrong abilities
module vulnerable::token2 {
    // Witness should only have drop
    public struct Witness has drop, store, copy {}

    public fun mint(_: Witness, amount: u64, ctx: &mut TxContext): Coin {
        Coin { id: object::new(ctx), value: amount }
    }
}
```

### Attack Scenario

1. Attacker sees Witness type with `drop` only but can be created publicly
2. Attacker creates Witness instance
3. Attacker calls mint function with forged witness
4. Attacker mints unlimited tokens

### Secure Code

```move
module secure::token {
    // GOOD: One-time witness (OTW) - can only be created at module init
    public struct WITNESS has drop {}

    // Only called once during module publish
    fun init(witness: WITNESS, ctx: &mut TxContext) {
        // Create collection with witness
        create_collection(witness, ctx);
    }

    // GOOD: Witness is passed as parameter, not creatable by users
    public fun mint(
        _: &mut WITNESS,  // Cannot be created by users
        amount: u64,
        ctx: &mut TxContext
    ): Coin {
        Coin { id: object::new(ctx), value: amount }
    }

    // Alternative: Use Publisher capability from Sui framework
    public fun mint_with_publisher(
        _: &Publisher,
        amount: u64,
        ctx: &mut TxContext
    ): Coin {
        // Publisher proves module ownership
        Coin { id: object::new(ctx), value: amount }
    }
}
```

### Testing

```move
#[test_only]
module secure::token_tests {
    use secure::token;

    #[test]
    #[expected_failure]
    fun test_cannot_create_witness() {
        // This should fail to compile - cannot create WITNESS outside module
        let witness = token::WITNESS {};
    }
}
```

---

## 4. Capability Leakage

### Description
Capabilities (admin, minter, etc.) transferred to unauthorized parties.

### Severity
HIGH

### Detection Pattern

```bash
# Find capability transfers
rg "transfer.*Cap" sources/
rg "public_transfer.*Cap" sources/

# Find capability creation
rg "AdminCap|MinterCap|OwnerCap" sources/
```

### Vulnerable Code

```move
module vulnerable::caps {
    public struct AdminCap has key { id: UID }

    // BAD: Anyone can claim admin capability
    public entry fun claim_admin_cap(
        recipient: address,
        ctx: &mut TxContext
    ) {
        transfer::public_transfer(
            AdminCap { id: object::new(ctx) },
            recipient
        );
    }

    // BAD: Capability can be redirected by any holder
    public entry fun transfer_admin_cap(
        cap: AdminCap,
        new_owner: address,
        ctx: &mut TxContext
    ) {
        transfer::public_transfer(cap, new_owner);
    }
}
```

### Attack Scenario

1. Attacker calls `claim_admin_cap` with their address
2. Attacker now has admin privileges
3. Attacker drains protocol funds or modifies critical parameters

### Secure Code

```move
module secure::caps {
    public struct AdminCap has key { id: UID }
    public struct CapState has key {
        id: UID,
        admin: address,
    }

    // GOOD: Only at module init, admin cap goes to publisher
    fun init(ctx: &mut TxContext) {
        transfer::public_transfer(
            AdminCap { id: object::new(ctx) },
            tx_context::sender(ctx)
        );
    }

    // GOOD: Require existing admin to transfer
    public entry fun transfer_admin_cap(
        _: &AdminCap,  // Must already have admin cap
        cap: AdminCap,
        new_admin: address,
        _ctx: &mut TxContext
    ) {
        transfer::public_transfer(cap, new_admin);
    }

    // GOOD: Multi-sig or timelock for sensitive operations
    public entry fun transfer_admin_with_delay(
        _: &AdminCap,
        _: &Timelock,
        cap: AdminCap,
        new_admin: address,
        _ctx: &mut TxContext
    ) {
        transfer::public_transfer(cap, new_admin);
    }
}
```

---

## 5. Improper Global Storage Access

### Description
Unchecked `borrow_global`, `borrow_global_mut`, or missing `acquires` leading to runtime errors or unexpected behavior.

### Severity
HIGH

### Detection Pattern

```bash
# Find global storage operations
rg "borrow_global|move_to|move_from|exists" sources/
rg "acquires" sources/
```

### Vulnerable Code

```move
module vulnerable::storage {
    public struct Balance has key { value: u64 }

    // BAD: No check if balance exists
    public entry fun withdraw(account: &mut signer, amount: u64): Balance {
        let addr = signer::address_of(account);
        // Will abort if Balance doesn't exist
        let balance = borrow_global_mut<Balance>(addr);
        assert!(balance.value >= amount, EInsufficientBalance);
        balance.value = balance.value - amount;
        Balance { value: amount }
    }

    // BAD: Race condition potential
    public entry fun transfer(from: &mut signer, to: address, amount: u64) {
        let addr = signer::address_of(from);
        let balance = borrow_global_mut<Balance>(addr);

        // Between this and the next borrow_global_mut, state could change
        // in concurrent transactions

        let dest = borrow_global_mut<Balance>(to);
        balance.value = balance.value - amount;
        dest.value = dest.value + amount;
    }
}
```

### Secure Code

```move
module secure::storage {
    public struct Balance has key { value: u64 }

    // GOOD: Check existence first
    public entry fun withdraw(account: &mut signer, amount: u64): Balance {
        let addr = signer::address_of(account);
        assert!(exists<Balance>(addr), EBalanceNotFound);

        let balance = borrow_global_mut<Balance>(addr);
        assert!(balance.value >= amount, EInsufficientBalance);
        balance.value = balance.value - amount;

        Balance { value: amount }
    }

    // GOOD: Atomic transfer with proper checks
    public entry fun transfer(from: &mut signer, to: address, amount: u64) acquires Balance {
        let addr = signer::address_of(from);

        // Check both exist
        assert!(exists<Balance>(addr), EBalanceNotFound);
        assert!(exists<Balance>(to), EDestNotFound);

        // Atomic borrow and modify
        let (src_balance, dest_balance) = (
            borrow_global_mut<Balance>(addr),
            borrow_global_mut<Balance>(to)
        );

        assert!(src_balance.value >= amount, EInsufficientBalance);

        src_balance.value = src_balance.value - amount;
        dest_balance.value = dest_balance.value + amount;
    }

    // GOOD: Initialize balance if not exists
    public entry fun deposit(account: &mut signer, balance: Balance) acquires Balance {
        let addr = signer::address_of(account);

        if (!exists<Balance>(addr)) {
            move_to(account, Balance { value: 0 });
        };

        let global = borrow_global_mut<Balance>(addr);
        let Balance { value } = balance;
        global.value = global.value + value;
    }
}
```

---

## 6. Arithmetic Issues

### Description
Overflow/underflow in calculations, especially in financial operations.

### Severity
MEDIUM

### Detection Pattern

```bash
# Find arithmetic operations
rg "\+|\-|\*" sources/
rg "checked_|saturating_" sources/
```

### Vulnerable Code

```move
module vulnerable::math {
    // BAD: Unchecked arithmetic
    public entry fun add_balance(
        balance: &mut Balance,
        amount: u64
    ) {
        balance.value = balance.value + amount; // Can overflow
    }

    // BAD: Subtraction without check
    public entry fun subtract(
        balance: &mut Balance,
        amount: u64
    ) {
        balance.value = balance.value - amount; // Can underflow
    }
}
```

### Secure Code

```move
module secure::math {
    // GOOD: Use checked arithmetic
    public entry fun add_balance(
        balance: &mut Balance,
        amount: u64
    ) {
        let new_value = balance.value + amount;
        assert!(new_value >= balance.value, EOverflow); // Overflow check
        balance.value = new_value;
    }

    // GOOD: Explicit bounds checking
    public entry fun subtract(
        balance: &mut Balance,
        amount: u64
    ) {
        assert!(balance.value >= amount, EUnderflow);
        balance.value = balance.value - amount;
    }

    // GOOD: Use safe math library
    public entry fun safe_add(
        balance: &mut Balance,
        amount: u64
    ) {
        balance.value = safe_math::add(balance.value, amount);
    }
}
```

---

## 7. Type Confusion

### Description
Improper use of generics or type casting leading to type confusion vulnerabilities.

### Severity
HIGH

### Detection Pattern

```bash
# Find generic usage
rg "T:|phantom|drop.*T" sources/
```

### Vulnerable Code

```move
module vulnerable::generics {
    // BAD: Improper generic constraints
    public struct Box<T> has key, store {
        value: T,
    }

    // Can store any type, including capabilities
    public entry fun store<T>(value: T, ctx: &mut TxContext) {
        transfer::public_transfer(
            Box { value },
            tx_context::sender(ctx)
        );
    }
}
```

### Secure Code

```move
module secure::generics {
    // GOOD: Proper constraints on generic types
    public struct Box<T: store> has key {
        id: UID,
        value: T,
    }

    // GOOD: Restrict what can be stored
    public entry fun store<T: store + drop>(
        value: T,
        ctx: &mut TxContext
    ) {
        transfer::public_transfer(
            Box { id: object::new(ctx), value },
            tx_context::sender(ctx)
        );
    }

    // GOOD: Use phantom for type markers without storing
    public struct Coin<phantom T> has key, store {
        id: UID,
        value: u64,
    }
}
```

---

## 8. Event Emission Issues

### Description
Missing, incorrect, or misleading event emissions that affect off-chain monitoring and auditing.

### Severity
LOW

### Detection Pattern

```bash
# Find event emissions
rg "sui::event|aptos::event|emit_event" sources/
```

### Vulnerable Code

```move
module vulnerable::events {
    // BAD: No events emitted for critical operations
    public entry fun transfer(
        _: &AdminCap,
        treasury: &mut Treasury,
        amount: u64,
        recipient: address,
        ctx: &mut TxContext
    ) {
        // Critical transfer with no event
        transfer::public_transfer(
            treasury::withdraw(treasury, amount),
            recipient
        );
    }
}
```

### Secure Code

```move
module secure::events {
    use sui::event;

    public struct TransferEvent has drop, copy {
        from: address,
        to: address,
        amount: u64,
        timestamp: u64,
    }

    // GOOD: Emit events for all critical operations
    public entry fun transfer(
        _: &AdminCap,
        treasury: &mut Treasury,
        amount: u64,
        recipient: address,
        ctx: &mut TxContext
    ) {
        let sender = tx_context::sender(ctx);

        event::emit(TransferEvent {
            from: sender,
            to: recipient,
            amount,
            timestamp: tx_context::timestamp(ctx),
        });

        transfer::public_transfer(
            treasury::withdraw(treasury, amount),
            recipient
        );
    }
}
```

---

## Summary Checklist

| Category | Check |
|----------|-------|
| Resource Abilities | Assets lack `copy` and `drop` |
| Access Control | All sensitive functions require capability |
| Witness Pattern | Witness types have only `drop` |
| Capability Leakage | Capabilities require existing auth to transfer |
| Global Storage | Check `exists` before `borrow_global` |
| Arithmetic | Use checked arithmetic for financial ops |
| Type Safety | Proper generic constraints |
| Events | Emit events for critical operations |

## references/aptos/ability-analysis.md

---
name: "ability-analysis"
description: "Trigger Pattern Always (Aptos Move) - foundational security check - Inject Into Breadth agents, depth agents"
---

# ABILITY_ANALYSIS Skill

> **Trigger Pattern**: Always (Aptos Move) --- foundational security check
> **Inject Into**: Breadth agents, depth agents

For every struct defined in the audited modules:

**STEP PRIORITY**: Steps 2 (Copy Ability Audit) and 6 (Ability Combination Analysis) are where HIGH/CRITICAL severity findings most commonly hide. Do NOT rush these steps. If constrained, skip conditional sections (7) before skipping 2 or 6.

## 1. Struct Ability Inventory

Enumerate ALL structs defined in the audited modules:

| Struct | Module | Abilities | Represents Value? | Represents Obligation? | Is Resource? | Security Assessment |
|--------|--------|-----------|-------------------|----------------------|-------------|---------------------|
| {name} | {module} | copy, drop, store, key | YES/NO | YES/NO | YES/NO | {assessment} |

**Classification guide**:
- **Value-bearing**: Coins, LP tokens, shares, receipts, vouchers, NFTs --- anything that represents transferable economic value or a claim to value
- **Obligation-bearing**: Hot potatoes, flash loan receipts, callback obligations, lock receipts --- anything that MUST be consumed before transaction ends
- **Resource**: Singleton state containers, registries, configuration stores --- things that should exist at most once per address/globally
- **Data**: Purely informational structs with no security-sensitive lifecycle (events, parameters, intermediate computation results)

**For each struct**: What abilities does it NEED vs what abilities does it HAVE? Excess abilities are the attack surface.

## 2. Copy Ability Audit

For each struct with the `copy` ability:

### 2a. Value Duplication Check

| Struct | Has `copy`? | Represents Value? | Duplication Exploitable? | Severity |
|--------|-------------|-------------------|-------------------------|----------|
| {name} | YES/NO | YES/NO | YES/NO --- {reason} | {H/M/L/N/A} |

**CRITICAL**: `copy` on a value-bearing type means the value can be duplicated at zero cost. This is the Move equivalent of a double-spend.

**Check for each `copy` struct**:
1. Can this struct be copied and then used multiple times? (e.g., copied receipt redeemed twice)
2. Does the module rely on move semantics to enforce single-use? If yes, `copy` breaks that assumption.
3. Is `copy` needed for legitimate operations? (e.g., snapshot reads, event emission) --- if not, it should be removed.
4. Trace all functions that accept this struct as a parameter: do they consume (move) or borrow (&) it? If they consume, copy lets callers retain the original.

**MANDATORY GREP**: Search all `.move` files for `has copy` and `copy,` in struct definitions. For each hit: (1) classify the struct, (2) if value-bearing, mark as FINDING.

### 2b. Copy-Then-Use Trace

For each `copy` struct identified as potentially dangerous:

```
1. Caller obtains instance I of struct S
2. Caller copies: I_copy = copy I
3. Caller uses I in function F1 (consumed/moved)
4. Caller uses I_copy in function F2 (consumed/moved)
5. Impact: {double-spend, double-claim, double-vote, obligation bypass}
```

Tag: `[TRACE:copy S → use1 in F1 → use2 in F2 → impact: {X}]`

## 3. Drop Ability Audit

For each struct with the `drop` ability:

### 3a. Obligation Bypass Check

| Struct | Has `drop`? | Represents Obligation? | Drop Bypasses Cleanup? | Severity |
|--------|-------------|----------------------|----------------------|----------|
| {name} | YES/NO | YES/NO | YES/NO --- {reason} | {H/M/L/N/A} |

**CRITICAL**: `drop` on an obligation-bearing struct means the obligation can be silently discarded. This is the Move equivalent of skipping a required finally-block.

**Hot potato pattern check**: The hot potato pattern relies on structs having NO `drop` ability, forcing the caller to pass them to a consuming function. If `drop` is present, the pattern is broken.

**Check for each `drop` struct**:
1. Is this struct a receipt or proof that must be returned to a specific function? (flash loan receipt, lock receipt, callback proof)
2. Does any function create this struct with the expectation that a corresponding "finalize" function will consume it?
3. What state changes happen in the finalize function? If the struct is dropped instead, those state changes never occur.
4. Does dropping this struct leave the protocol in an inconsistent state? (borrowed funds not returned, locks not released, counters not decremented)

### 3b. Drop-Instead-of-Consume Trace

For each obligation struct:

```
1. Function F_create creates struct S (e.g., flash_loan returns receipt)
2. EXPECTED: Caller passes S to F_consume (e.g., repay(receipt))
3. ACTUAL (if drop): Caller drops S, F_consume never called
4. Impact: {funds not returned, lock not released, state inconsistent}
```

Tag: `[TRACE:drop obligation S → F_consume skipped → impact: {X}]`

## 4. Store Ability Audit

For each struct with the `store` ability:

### 4a. Module Control Escape Check

| Struct | Has `store`? | Can Escape Module? | Invariant Break If Escaped? | Severity |
|--------|-------------|-------------------|---------------------------|----------|
| {name} | YES/NO | YES --- via {mechanism} / NO | YES/NO --- {which invariant} | {H/M/L/N/A} |

**Check for each `store` struct**:
1. `store` allows the struct to be placed inside other structs, into `Table`/`SmartTable`, or moved to global storage via a wrapping resource. Can an attacker store this struct in their own resource, bypassing module-controlled access?
2. Does the module rely on controlling where instances of this struct live? If instances escape to user-controlled storage, can they be replayed, hoarded, or used out of context?
3. For structs with `store` but without `key`: can they be wrapped in a user-defined `key` struct to achieve unauthorized global storage?
4. If the struct contains mutable references to shared state (e.g., via `&mut` in the functions that operate on it), does escaping the module allow stale or orphaned references?

### 4b. Unauthorized Persistence Trace

For structs not intended to persist outside module control:

```
1. Module M creates struct S with `store` ability
2. Attacker wraps S in their own struct W (has key + store)
3. Attacker calls move_to<W>(@attacker, W { s: obtained_S })
4. S now persists at attacker's address outside M's control
5. Impact: {replay, hoarding, context-escape, stale state}
```

## 5. Key Ability Audit

For each struct with the `key` ability:

### 5a. Resource Lifecycle Check

| Struct | Has `key`? | Intended as Global Resource? | move_from Protected? | move_to Protected? | Severity |
|--------|-----------|----------------------------|---------------------|-------------------|----------|
| {name} | YES/NO | YES/NO | YES --- {by what} / NO | YES --- {by what} / NO | {H/M/L/N/A} |

**Check for each `key` struct**:
1. Who can call `move_to` for this resource? Is creation properly gated by access control (signer capability, admin checks)?
2. Who can call `move_from` for this resource? Can an attacker remove a critical resource from an address?
3. Is the resource intended to be a singleton (one per address/globally)? If yes, can an attacker cause duplicate creation or premature deletion?
4. Does the module use `exists<S>(addr)` checks? Can an attacker manipulate resource existence to bypass guards?
5. For resources published at a shared/module address: what happens if the resource is removed? Does the protocol become non-functional?

### 5b. Resource Deletion Impact

For each resource that other functions depend on:

| Resource | Functions That Read It | Functions That Require exists<S> | Impact If Deleted |
|----------|----------------------|--------------------------------|-------------------|
| {name} | {list} | {list} | {abort, DoS, state corruption} |

## 6. Ability Combination Analysis

Analyze dangerous ability combinations:

| Struct | Abilities | Combination Risk | Attack Vector | Severity |
|--------|-----------|-----------------|---------------|----------|
| {name} | copy + store | Replicate and persist duplicates in global storage | Infinite value creation via copy then store each copy | Critical |
| {name} | drop + key | Abandon a top-level resource | Delete critical protocol state, DoS | High |
| {name} | copy + drop | Infinite creation + no cleanup obligation | Value duplication with no consumption requirement | Critical (if value-bearing) |
| {name} | copy + drop + store | All of the above combined | Maximum exploitation surface | Critical (if value-bearing) |
| {name} | key + copy | Resource duplication at global level | Move resource to address, copy, move copy elsewhere | High |

**MANDATORY**: For every value-bearing or obligation-bearing struct, verify that NONE of these dangerous combinations are present. If present, classify as FINDING with severity based on the struct's role.

**Safe combinations**:
- `store` alone on data structs (stored inside other resources, no standalone risk)
- `copy + drop` on purely informational structs (events, read-only parameters)
- `key + store + drop` on administrative resources with proper access control

## 7. Generic Type Parameter Abilities

For every generic struct and generic function in the audited modules:

### 7a. Generic Struct Constraints

| Struct | Type Param | Constraint | Sufficient? | Unexpected Instantiation? |
|--------|-----------|-----------|-------------|--------------------------|
| `Wrapper<T: store>` | T | store | {analysis} | {can attacker use T = MaliciousType?} |

**Check**:
1. Is the ability constraint on the type parameter the MINIMUM required? Overly permissive constraints (e.g., `T: store + copy + drop` when only `store` is needed) expand the attack surface.
2. Can an attacker instantiate the generic with a type that has unexpected properties? Example: `Pool<T: store>` instantiated with a custom token type that has transfer hooks or non-standard behavior.
3. For phantom type parameters (`phantom T`): does the module correctly use them for type-level discrimination without relying on runtime properties of T?
4. Do ability constraints on generic parameters match the constraints required by all functions that operate on the containing struct?

### 7b. Ability Constraint Mismatch

Check for mismatches between struct definition and function signatures:

```
struct Container<T: store> has key, store { item: T }

// POTENTIAL ISSUE: Function requires T: copy + store, but Container only requires T: store
// Can Container be created with a non-copy T, then this function fails?
public fun clone_item<T: copy + store>(c: &Container<T>): T { *&c.item }
```

**Impact**: If a module publishes a Container<NonCopyType>, the clone_item function aborts at runtime. Is this a DoS vector?

## Finding Template

When this skill identifies an issue:

```markdown
**ID**: [AB-N]
**Severity**: [based on struct role and exploitation impact]
**Step Execution**: check1,2,3,4,5,6,7 | X(reasons) | ?(uncertain)
**Rules Applied**: [R4:Y, R5:Y, R10:Y, R17:Y]
**Location**: module::struct_name (source_file.move:LineN)
**Title**: [Struct] has [ability] enabling [attack: duplication/obligation bypass/escape/deletion]
**Description**: [Trace the ability exploitation from struct definition to impact]
**Impact**: [What breaks: double-spend, obligation bypass, state corruption, DoS]
```

---

## Step Execution Checklist (MANDATORY)

> **CRITICAL**: You MUST report completion status for ALL sections. Steps 2 and 6 are highest priority.

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Struct Ability Inventory | YES | Y/X/? | Enumerate ALL structs |
| 2. Copy Ability Audit | **YES** | Y/X/? | **MANDATORY** --- highest-severity source |
| 2b. Copy-Then-Use Trace | IF copy on value type | Y/X(N/A)/? | |
| 3. Drop Ability Audit | YES | Y/X/? | Hot potato pattern check |
| 3b. Drop-Instead-of-Consume Trace | IF drop on obligation type | Y/X(N/A)/? | |
| 4. Store Ability Audit | YES | Y/X/? | Module control escape |
| 5. Key Ability Audit | YES | Y/X/? | Resource lifecycle |
| 5b. Resource Deletion Impact | IF key resources found | Y/X(N/A)/? | |
| 6. Ability Combination Analysis | **YES** | Y/X/? | **MANDATORY** --- dangerous combos |
| 7. Generic Type Parameter Abilities | IF generics present | Y/X(N/A)/? | Constraint sufficiency |

### Cross-Reference Markers

**After Section 2** (Copy Ability Audit):
- IF copy on value-bearing struct found -> cross-reference with `TYPE_SAFETY.md` Section 2 for type substitution amplification
- IF copy enables double-use -> severity minimum HIGH

**After Section 3** (Drop Ability Audit):
- IF drop on obligation struct found -> cross-reference with token flow analysis for flash loan receipt handling
- IF drop bypasses repayment -> severity minimum CRITICAL

**After Section 6** (Ability Combination Analysis):
- IF dangerous combination found on value-bearing struct -> severity minimum HIGH
- Document all structs with safe ability justification for audit trail

## references/aptos/attack-vectors.md

# Aptos Move Attack Vectors Catalog

Known attack vectors for Aptos Move smart contracts, organized by category. Each vector includes detection patterns.

---

## 1. Ability Exploitation

### 1.1 Asset Duplication via `copy`
- **Severity**: CRITICAL
- **Pattern**: Struct representing value has `copy` ability
- **Impact**: Unlimited fund duplication
- **Detection**: `rg "public struct.*(Coin|Token|Asset|Balance).*has.*copy" sources/`

### 1.2 Silent Loss via `drop`
- **Severity**: CRITICAL
- **Pattern**: Struct representing value has `drop` ability
- **Impact**: Funds silently discarded
- **Detection**: `rg "public struct.*(Coin|Token|Asset|Balance).*has.*drop" sources/`

---

## 2. Access Control Bypass

### 2.1 Ungated Entry Functions
- **Severity**: CRITICAL
- **Pattern**: `public entry fun` without signer validation
- **Impact**: Unauthorized privileged operations
- **Detection**: `rg "public entry fun" sources/ | grep -v "signer"`

### 2.2 Signer Validation Bypass
- **Severity**: CRITICAL
- **Pattern**: `&signer` parameter used without validating the address
- **Impact**: Unauthorized operations on behalf of any account
- **Detection**: `rg "public entry fun.*signer" sources/` then check for `assert!` on signer address

### 2.3 Capability Claiming
- **Severity**: HIGH
- **Pattern**: Function creates and transfers capability without authorization
- **Impact**: Anyone gains admin/minter privileges
- **Detection**: `rg "public.*fun.*claim\|public.*fun.*create.*Cap.*transfer" sources/`

---

## 3. Witness and Type System Abuse

### 3.1 Forgeable Witness
- **Severity**: CRITICAL
- **Pattern**: Witness struct can be instantiated outside module `init`
- **Impact**: Unauthorized type creation, token minting
- **Detection**: `rg "public struct.*has drop" sources/` then verify struct is not OTW

### 3.2 Witness Reuse
- **Severity**: HIGH
- **Pattern**: Witness has `store` or `copy`, allowing it to be saved and reused
- **Impact**: Witness consumed in init but copy retained for later abuse
- **Detection**: `rg "public struct.*Witness.*has.*(store|copy)" sources/`

### 3.3 Generic Type Confusion
- **Severity**: HIGH
- **Pattern**: Generic functions without proper ability constraints
- **Impact**: Store/extract unauthorized types
- **Detection**: `rg "public fun.*<T>" sources/ | grep -v "phantom\|store\|key"`

---

## 4. Concurrency and State Issues

### 4.1 Stale Parameters
- **Severity**: MEDIUM
- **Pattern**: Parameters read from storage at beginning of multi-step operation, but state may change between steps
- **Impact**: Operations based on stale data
- **Detection**: Look for sequential `borrow_global` calls or multi-step operations

---

## 5. Economic Attacks

### 5.1 Flash Loan Attack
- **Severity**: HIGH
- **Pattern**: Price/oracle manipulation within a single transaction using borrowed funds
- **Impact**: Draining liquidity pools, manipulating prices
- **Detection**: `rg "flash\|loan\|borrow.*deposit" sources/`

### 5.2 First Depositor / Zero-State Issue
- **Severity**: MEDIUM
- **Pattern**: Division by zero or rate manipulation when vault/pool has zero or near-zero deposits
- **Impact**: First depositor gets inflated share ratio
- **Detection**: `rg "shares.*total_supply\|balance.*\.value.*/" sources/`

### 5.3 Rounding Exploitation
- **Severity**: MEDIUM
- **Pattern**: Integer division truncation that can be exploited for small gains at scale
- **Impact**: Systematic value extraction
- **Detection**: `rg " \/ " sources/` in financial calculation contexts

### 5.4 Share Inflation Attack
- **Severity**: HIGH
- **Pattern**: Attacker deposits dust, donates inflated tokens, then redeems for disproportionate share
- **Impact**: Theft of other depositors' funds
- **Detection**: Look for vault deposit/withdraw without minimum deposit or offset

---

## 6. Storage and State Management

### 6.1 Unbounded Storage Growth
- **Severity**: MEDIUM
- **Pattern**: Vectors or tables that can grow without limit
- **Impact**: DoS via gas exhaustion, storage bloat
- **Detection**: `rg "vector::push_back\|table::add" sources/` without size checks

### 6.2 Missing `exists` Check
- **Severity**: HIGH
- **Pattern**: `borrow_global` without prior `exists` check
- **Impact**: Runtime abort, potential DoS
- **Detection**: `rg "borrow_global" sources/` then verify preceding `exists` or `assert!`

---

## 7. Aptos-Specific Attack Vectors

### 7.1 Reentrancy via FA Hooks (Move 2.2+)
- **Severity**: HIGH
- **Pattern**: FungibleAsset dispatch hooks allow callbacks during transfer/mint
- **Impact**: Reentrancy-style attacks
- **Detection**: `rg "dispatch\|hook\|FungibleAsset" sources/`

### 7.2 Ref Lifecycle Abuse
- **Severity**: HIGH
- **Pattern**: MintRef, BurnRef, TransferRef not properly secured or stored
- **Impact**: Unauthorized minting, burning, or transfers
- **Detection**: `rg "MintRef\|BurnRef\|TransferRef" sources/`

### 7.3 SignerCapability Leakage
- **Severity**: CRITICAL
- **Pattern**: SignerCapability stored in accessible resource or transferred
- **Impact**: Attacker can act as any account
- **Detection**: `rg "SignerCapability\|signer_capability" sources/`

---

## Attack Vector Matrix (Aptos)

| Vector | Severity |
|--------|----------|
| Asset `copy` ability | CRITICAL |
| Asset `drop` ability | CRITICAL |
| Forgeable Witness | CRITICAL |
| Ungated entry function | CRITICAL |
| Signer bypass | CRITICAL |
| SignerCapability leakage | CRITICAL |
| Capability leakage | HIGH |
| Flash loan manipulation | HIGH |
| Generic type confusion | HIGH |
| FA hook reentrancy | HIGH |
| Ref lifecycle abuse | HIGH |
| Unbounded storage | MEDIUM |
| Stale parameters | MEDIUM |
| First depositor | MEDIUM |
| Rounding exploitation | MEDIUM |

## references/aptos/bit-shift-safety.md

---
name: "bit-shift-safety"
description: "Trigger Pattern Always (Aptos Move) - Move VM aborts on shift = bit width - Inject Into Breadth agents, depth-edge-case"
---

# BIT_SHIFT_SAFETY Skill

> **Trigger Pattern**: Always (Aptos Move) --- Move VM aborts on shift >= bit width
> **Inject Into**: Breadth agents, depth-edge-case

The Move VM performs a runtime check on every bit shift operation: if the shift amount is greater than or equal to the bit width of the operand type, the transaction aborts. This is not a silent wraparound --- it is a hard abort that reverts the entire transaction. Any user-controllable or computed shift amount that can reach the bit width threshold is a denial-of-service vector.

## 1. Shift Operation Inventory

**MANDATORY GREP**: Search all `.move` files for `<<` and `>>` operators.

For each shift operation found:

| Location (file:line) | Operand Type | Bit Width | Shift Amount Source | User-Controllable? | Bounded? |
|-----------------------|-------------|-----------|--------------------|--------------------|----------|
| {file}:{line} | u8/u16/u32/u64/u128/u256 | 8/16/32/64/128/256 | constant / parameter / computed | YES/NO | YES/NO --- {how} |

**Classification of shift amount sources**:
- **Constant**: Hardcoded literal (e.g., `1 << 64`). Safe if < bit width, abort if >= bit width. Check constants that equal or exceed the bit width --- this is a compile-time-detectable bug but Move does not always catch it.
- **Parameter**: Passed into the function from a caller. Trace the call chain to determine if externally controllable.
- **Computed**: Result of arithmetic (e.g., `1 << (decimals - offset)`). Requires boundary analysis.

## 2. Shift Amount Bound Verification

For each shift operation where the shift amount is NOT a safe constant:

### 2a. Bit Width Threshold Table

| Type | Bit Width | Max Safe Shift | Abort Condition |
|------|-----------|---------------|-----------------|
| u8 | 8 | 7 | shift >= 8 |
| u16 | 16 | 15 | shift >= 16 |
| u32 | 32 | 31 | shift >= 32 |
| u64 | 64 | 63 | shift >= 64 |
| u128 | 128 | 127 | shift >= 128 |
| u256 | 256 | 255 | shift >= 256 |

### 2b. Bound Verification Per Shift

For each non-constant shift:

| Location | Shift Amount Expression | Minimum Value | Maximum Value | Exceeds Bit Width? | Guard Present? |
|----------|------------------------|---------------|---------------|-------------------|----------------|
| {location} | {expression} | {min} | {max} | YES/NO | YES --- {assert/min/if} / NO |

**Verification method**: Trace the shift amount back to its origin. For each variable in the expression:
1. What is its declared type? (constrains range)
2. Is there an `assert!()` that bounds it before the shift?
3. Is there a `min()` or `if` guard?
4. Can the variable be set by an external caller (entry function parameter, stored value set by a public function)?

Tag: `[BOUNDARY:shift_amount={val} → abort at bit_width={W}]`

## 3. Computed Shift Analysis

For shift amounts derived from arithmetic, perform boundary value analysis:

### 3a. Subtraction Underflow in Shift Amount

Pattern: `1 << (a - b)` where both `a` and `b` are unsigned integers.

| Location | Expression | Can `b > a`? | Underflow Result | Impact |
|----------|-----------|-------------|-----------------|--------|
| {location} | `1 << (decimals - 6)` | YES if decimals < 6 | Wraps to large u8/u64 → abort | DoS |

**Check**: Move unsigned subtraction aborts on underflow (no wraparound). So `a - b` where `b > a` aborts BEFORE the shift. This is a separate DoS vector (arithmetic underflow). Document both:
1. Underflow abort if `b > a`
2. Shift abort if `a - b >= bit_width`

### 3b. Addition/Multiplication Overflow in Shift Amount

Pattern: `value << (a + b)` or `value << (a * b)`

| Location | Expression | Can Sum/Product >= Bit Width? | Impact |
|----------|-----------|------------------------------|--------|
| {location} | {expression} | YES/NO --- {boundary values} | {DoS / safe} |

### 3c. Shift Result Overflow

Even if the shift amount is safe, the RESULT of the shift may overflow the type:

| Location | Expression | Operand Max Value | Shift Amount | Result Exceeds Type Max? | Impact |
|----------|-----------|-------------------|-------------|-------------------------|--------|
| {location} | `amount << decimals` | {max} | {amount} | YES/NO | {silent truncation / abort} |

**Note**: Move does NOT abort on shift result overflow --- the result is silently truncated (high bits discarded). This is a correctness bug, not a DoS bug, but can cause incorrect calculations (e.g., `1u64 << 63` = 9223372036854775808, but `3u64 << 63` = 9223372036854775808 due to truncation).

Tag: `[BOUNDARY:shift_result=truncated at type_max]`

## 4. DoS Impact Assessment

For each shift operation that can abort:

### 4a. Abort Impact Trace

| Location | Function | Entry Point? | Who Calls This? | Abort Blocks What? | Severity |
|----------|----------|-------------|----------------|--------------------|---------|
| {location} | {function} | YES/NO | {callers} | {blocked operations} | {H/M/L} |

**Trace from the aborting shift outward**:
1. Which function contains the shift?
2. Is that function called by entry functions (user-facing)?
3. Is it called in a critical path (deposit, withdraw, claim, liquidation)?
4. Can an attacker provide input that triggers the abort?
5. Does the abort affect ONLY the attacker's transaction, or does it block other users?

**Severity guide**:
- Shift in view function only -> Low (informational, no state impact)
- Shift in user's own transaction path (self-DoS only) -> Low
- Shift in shared operation (affects all users) -> Medium to High
- Shift in critical path (deposits/withdrawals blocked for all) triggered by attacker input -> High
- Shift in liquidation/price computation path -> High to Critical (can block liquidations, enable insolvency)

### 4b. Attacker-Triggerable Analysis

For shifts that can abort and are in shared/critical paths:

```
1. Attacker calls entry function F with parameter P
2. P flows through {trace} to shift operation at {location}
3. Shift amount becomes {expression} which equals {value} >= {bit_width}
4. Transaction aborts
5. Impact: {what is blocked for other users}
6. Cost to attacker: {gas cost only / requires tokens / requires role}
7. Persistence: {one-time / repeatable / permanent state corruption}
```

Tag: `[TRACE:attacker input P={val} → shift abort → {blocked_operation} DoS]`

## 5. Safe Shift Patterns

Document which shifts in the codebase follow safe patterns (for completeness and to confirm analysis coverage):

| Pattern | Example | Why Safe |
|---------|---------|----------|
| Constant shift < bit width | `1u64 << 32` | 32 < 64, always safe |
| Bounded by min() | `1 << min(amount, 63)` | Capped below bit width |
| Guarded by assert | `assert!(shift < 64, E_INVALID); val << shift` | Explicit pre-check |
| Type-constrained | `(x as u8) << 4` where x comes from a u8 field | u8 max = 255, but shift amount 4 is constant |
| Bounded by protocol invariant | `decimals` is always 6 or 8 (set once, immutable) | Document the invariant and verify immutability |

**RULE**: A shift is only "safe by protocol invariant" if the invariant is ENFORCED on-chain (assert, type constraint, immutable field set in constructor). Documentation-only invariants do NOT qualify.

## Finding Template

When this skill identifies an issue:

```markdown
**ID**: [BS-N]
**Severity**: [based on DoS scope and attacker controllability]
**Step Execution**: check1,2,3,4,5 | X(reasons) | ?(uncertain)
**Rules Applied**: [R2:Y, R4:Y, R10:Y]
**Depth Evidence**: [BOUNDARY:shift_amount={val}], [TRACE:input→abort→impact]
**Location**: module::function (source_file.move:LineN)
**Title**: Unbounded bit shift in [function] enables [DoS/incorrect calculation]
**Description**: [Trace from input to shift operation to abort/truncation to impact]
**Impact**: [What is blocked or miscalculated, who is affected, persistence]
```

---

## Step Execution Checklist (MANDATORY)

> **CRITICAL**: You MUST report completion status for ALL sections. Sections 1-2 are mechanical and must never be skipped.

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Shift Operation Inventory | **YES** | Y/X/? | **MANDATORY** --- grep ALL .move files |
| 2. Shift Amount Bound Verification | YES | Y/X/? | For each non-constant shift |
| 3. Computed Shift Analysis | IF computed shifts found | Y/X(N/A)/? | Subtraction underflow + result overflow |
| 3c. Shift Result Overflow | IF shifts with variable operands | Y/X(N/A)/? | Silent truncation check |
| 4. DoS Impact Assessment | IF any shift can abort | Y/X(N/A)/? | Trace to user impact |
| 4b. Attacker-Triggerable Analysis | IF abort in shared/critical path | Y/X(N/A)/? | Full attack trace |
| 5. Safe Shift Patterns | YES | Y/X/? | Confirm safe shifts for coverage |

### Cross-Reference Markers

**After Section 1** (Shift Operation Inventory):
- IF zero shifts found in codebase -> mark all sections X(N/A), write "No bit shift operations found in audited modules" and STOP
- IF shifts found -> proceed to Section 2, do NOT skip

**After Section 4** (DoS Impact Assessment):
- Cross-reference with access control analysis: can the aborting function be called permissionlessly?
- Cross-reference with oracle/price analysis: are shifts used in price computations? If yes, abort = price oracle DoS -> severity escalation
- IF shift in liquidation path -> severity minimum HIGH

**After Section 5** (Safe Shift Patterns):
- Verify: every shift from Section 1 is accounted for in EITHER Section 2 (unsafe) or Section 5 (safe)
- Any unaccounted shift -> reanalyze before finalizing

## references/aptos/centralization-risk.md

---
name: "centralization-risk"
description: "Trigger Protocol has privileged roles (admin, operator, governance, resource account owner) - Covers Single points of failure, privilege escalation, external governance dependen..."
---

# Skill: CENTRALIZATION_RISK

> **Trigger**: Protocol has privileged roles (admin, operator, governance, resource account owner)
> **Covers**: Single points of failure, privilege escalation, external governance dependencies
> **Required**: NO (optional -- recommended when protocol has 3+ distinct privileged roles)
> **Inject Into**: Breadth agents

## Trigger Patterns

```
admin|owner|operator|governance|signer_cap|SignerCapability|resource_account|
has_role|is_admin|only_admin|assert_admin|get_signer
```

## Aptos Capability Model Context

Aptos Move uses a capability-based access control model fundamentally different from EVM modifiers:
- **Signer-based**: Functions receive `&signer` and check `signer::address_of(account) == @admin`
- **Capability pattern**: `SignerCapability` stored in resources grants signing rights to resource accounts
- **No modifiers**: Access control is enforced via `assert!` checks inside function bodies
- **Resource accounts**: Accounts controlled by `SignerCapability` rather than a private key
- **Object ownership**: `Object<T>` has an owner chain; ownership transfers control access

## Reasoning Template

### Step 1: Privilege Inventory

Enumerate ALL capability-gated functions by searching for signer checks and capability usage:

| # | Function | Module | Access Gate | What It Controls | Impact If Abused |
|---|----------|--------|------------|------------------|-----------------|
| 1 | {func} | {module} | `assert!(addr == @admin)` | {parameter/state} | {worst case} |
| 2 | {func} | {module} | `SignerCapability` stored in {resource} | {operation} | {worst case} |
| 3 | {func} | {module} | `Object<T>` ownership check | {asset control} | {worst case} |

**MANDATORY GREP**: Search all `.move` files for:
- `signer::address_of` followed by equality checks
- `SignerCapability` usage (creation, storage, `account::create_signer_with_capability`)
- `object::is_owner` and ownership assertions
- Named address references (`@admin`, `@operator`, `@governance`, `@protocol`)

**Categorize each by impact**:
- **FUND_CONTROL**: Can move, lock, freeze, or destroy user funds/assets
- **PARAMETER_CONTROL**: Can change fees, rates, thresholds, delays
- **OPERATIONAL_CONTROL**: Can pause, unpause, add/remove components, whitelist/blacklist
- **UPGRADE_CONTROL**: Can upgrade module code (publish new version)

### Step 2: Role Hierarchy and Capability Delegation

Map the capability hierarchy:

| Role/Capability | Granted By | Can Delegate? | Stored Where? | Revocable? | Timelock? |
|----------------|-----------|---------------|--------------|-----------|-----------|
| Admin signer | Deployment (named address) | NO (fixed) | N/A -- address-based | NO (immutable) | NO |
| SignerCapability | account::create_resource_account | YES (if stored with `store`) | {resource at @addr} | {depends on module logic} | {YES/NO} |
| Object owner | object::transfer | YES (transfer ownership) | Object metadata | YES (transfer away) | NO |

**Aptos-specific checks**:
- [ ] Are FUND_CONTROL and UPGRADE_CONTROL separated into different addresses/capabilities?
- [ ] Does any single address have both PARAMETER_CONTROL and FUND_CONTROL?
- [ ] Can `SignerCapability` be duplicated? (if the resource containing it has `copy` ability -- CRITICAL)
- [ ] Can capabilities be extracted from the storing resource by anyone? (check resource field visibility)
- [ ] Is the resource account SignerCapability stored behind proper access control?

### Step 3: Single Points of Failure

For each privileged role:

| Role | Key Compromise Impact | Mitigation | Residual Risk |
|------|----------------------|------------|---------------|
| @admin (EOA) | {what attacker can do} | {multisig? module-level checks?} | {what remains} |
| Resource account | {what attacker can do if SignerCapability leaked} | {capability stored in immutable resource?} | {what remains} |
| Object owner | {what attacker can do with object control} | {ownership transfer gated?} | {what remains} |

**Severity assessment**:
- Single EOA address with FUND_CONTROL -> HIGH centralization risk
- Multisig controlling admin address (off-chain, not verifiable on-chain) -> MEDIUM
- Resource account with properly guarded SignerCapability -> LOW (but document)
- Module published as `immutable` -> eliminates UPGRADE_CONTROL risk entirely

**Aptos-specific risk**: `SignerCapability` is the most dangerous capability -- it grants FULL control over the resource account, including publishing modules and transferring all assets. If the resource containing the capability has improper access control, it is equivalent to leaking a private key.

### Step 4: External Governance Dependencies

Identify parameters or behaviors controlled by EXTERNAL governance:

| Dependency | External Entity | What They Control | Protocol Impact If Changed | Notification? |
|------------|----------------|-------------------|---------------------------|---------------|
| {dep} | {entity} | {parameter/behavior} | {impact on this protocol} | YES/NO |

**Aptos-specific patterns**:
- **Framework governance**: `aptos_framework` parameters controlled by Aptos governance (staking, gas, transaction limits)
- **External module upgrades**: Modules the protocol depends on upgrading under `compatible` policy -- new abort conditions, changed behavior
- **Oracle operator changes**: Oracle price feed operators changing configs, adding latency, pausing feeds
- **Bridge governance**: Wormhole guardian set changes, LayerZero oracle/relayer config

**Check**:
- Can external governance changes break protocol invariants?
- Does the protocol have circuit breakers for external changes?
- Are external governance timelines aligned with this protocol operational timelines?

### Step 5: Emergency Powers

Document emergency/pause capabilities:

| Emergency Function | Who Can Call | What It Affects | Recovery Path | Time to Recover |
|-------------------|-------------|-----------------|---------------|-----------------|
| {func} | {role/address} | {scope} | {how to resume} | {estimate} |

**Aptos-specific checks**:
- [ ] Can pausing strand user funds permanently? (resources stay in global storage but no exit path)
- [ ] Is there a maximum pause duration enforced on-chain?
- [ ] Can users exit during pause (emergency withdraw function)?
- [ ] If module is published as `immutable` and paused -> permanent freeze? (no upgrade possible)
- [ ] Can the freeze/blacklist mechanism on FungibleAsset be used as an emergency power?
- [ ] If no exit during pause -> apply Rule 9 (stranded asset severity floor)

## Instantiation Parameters

```
{CONTRACTS}           -- List of modules to analyze
{ADMIN_ADDRESSES}     -- Named addresses with privileged access (@admin, @operator, etc.)
{CAPABILITY_RESOURCES} -- Resources that store SignerCapability or other capabilities
{EXTERNAL_DEPS}       -- External modules with governance dependencies
```

## Output Schema

```markdown
## Finding [CR-N]: Title

**Verdict**: CONFIRMED / PARTIAL / REFUTED
**Step Execution**: check1,2,3,4,5 | X(reason) | ?(uncertain)
**Severity**: Critical/High/Medium/Low/Info
**Location**: module::function (source_file.move:LineN)

**Centralization Type**: FUND_CONTROL / PARAMETER_CONTROL / OPERATIONAL_CONTROL / UPGRADE_CONTROL
**Affected Role**: {role_name / address / capability}
**Mitigation Present**: {multisig/timelock/immutable module/NONE}

**Description**: What is wrong
**Impact**: What can happen if role is compromised or acts maliciously
**Recommendation**: How to mitigate (add timelock module, separate capabilities, publish immutable)
```

## Step Execution Checklist

- [ ] Step 1: ALL privileged functions enumerated (via grep for signer checks + capabilities)
- [ ] Step 2: Capability hierarchy mapped with delegation analysis
- [ ] Step 3: Single points of failure identified for each role/capability
- [ ] Step 4: External governance dependencies documented
- [ ] Step 5: Emergency powers and recovery paths assessed

## references/aptos/cross-chain-timing.md

---
name: "cross-chain-timing"
description: "Trigger Pattern wormhole|layerzero|ccip|bridge|cross_chain|vaa|guardian|emitter|relay|remote_chain|payload|nonce.sequence - Inject Into Breadth agents, depth-external"
---

# CROSS_CHAIN_TIMING Skill (Aptos)

> **Trigger Pattern**: `wormhole|layerzero|ccip|bridge|cross_chain|vaa|guardian|emitter|relay|remote_chain|payload|nonce.*sequence`
> **Inject Into**: Breadth agents, depth-external
> **Finding prefix**: `[CCT-N]`
> **Rules referenced**: R1, R2, R4, R8, R10, R16

Covers: cross-chain message verification, timing asymmetry between Aptos and other chains, resource creation requirements, nonce/sequence replay protection, and cross-chain price relay staleness.

Aptos's fast finality (~1 second with BFT consensus) creates a fundamental timing asymmetry with slower chains (Ethereum ~12min, rollups 10-60min). This asymmetry is the primary attack vector for cross-chain timing exploits on Aptos. Additionally, Move's type-safe resource model introduces unique account/resource requirements for cross-chain operations.

---

## Step 1: Identify Cross-Chain Messaging Infrastructure

Find all cross-chain messaging calls and infrastructure:

| # | Bridge/Protocol | Direction | Aptos Function | Remote Chain | Message Type |
|---|----------------|-----------|---------------|-------------|-------------|
| 1 | {Wormhole/LayerZero/CCIP/custom} | {Aptos->Remote / Remote->Aptos} | {function name} | {Ethereum/Arbitrum/etc.} | {token transfer / state sync / price relay / governance} |

### Wormhole-Specific Inventory
If Wormhole is detected:

| Component | Module/Function | Purpose | Location |
|-----------|----------------|---------|----------|
| VAA Verification | `vaa::parse_and_verify` / guardian signature check | Guardian signature verification | {file:line} |
| Message Posting | `wormhole::publish_message` | Send message from Aptos | {file:line} |
| Token Bridge | `complete_transfer` / `create_wrapped_coin` | Token bridging | {file:line} |
| Emitter Resource | Emitter capability or resource | Message source identity | {file:line} |

### LayerZero-Specific Inventory
If LayerZero is detected:

| Component | Module/Function | Verification Method | Location |
|-----------|----------------|-------------------|----------|
| Endpoint | `endpoint::lz_receive` / receive handler | Oracle + Relayer attestation | {file:line} |
| Remote Mapping | Trusted remote configuration | Address/chain validation | {file:line} |
| Nonce Tracking | Inbound/outbound nonce resources | Replay prevention | {file:line} |

### Generic Bridge Inventory
For custom or other bridges:

| Component | Module/Function | Verification Method | Location |
|-----------|----------------|-------------------|----------|
| Message Resource | {resource type} | {signature/merkle/optimistic} | {file:line} |
| Relayer | {relayer constraint} | {how relayer is validated} | {file:line} |
| Nonce Tracking | {nonce storage} | {replay prevention method} | {file:line} |

---

## Step 2: Cross-Chain Message Verification Audit

For EACH inbound cross-chain message consumed by the module:

### 2a. Wormhole VAA Verification Checklist

| # | Check | Status | Location | Notes |
|---|-------|--------|----------|-------|
| 1 | Guardian signature count >= quorum (13/19) | YES/NO | {line} | Does module verify `guardian_set_index` is current? |
| 2 | Guardian set is current (not expired) | YES/NO | {line} | Old guardian sets may be compromised |
| 3 | Emitter chain ID validated | YES/NO | {line} | Reject messages from unexpected source chains |
| 4 | Emitter address validated | YES/NO | {line} | Reject messages from unexpected contracts on source chain |
| 5 | Sequence number replay check | YES/NO | {line} | Each VAA sequence should be processed exactly once |
| 6 | Consistency level validated | YES/NO | {line} | `finalized` vs `confirmed` - determines security guarantee |
| 7 | Payload format validated | YES/NO | {line} | Malformed payload handling - Move's `bcs::from_bytes` may abort on bad data |
| 8 | VAA resource authenticity | YES/NO | {line} | Is the VAA resource created by the Wormhole module (not user-supplied)? |

**Critical**: Missing checks 1-5 = **CRITICAL** (arbitrary cross-chain message injection). Missing checks 6-8 = **HIGH** (message quality/integrity issues).

**Aptos-specific**: Move's type system provides some protection - a `VAA` resource type can only be created by the Wormhole module. However, verify that the consuming module checks the VAA was created by the CORRECT Wormhole deployment (not a cloned module at a different address).

### 2b. Generic Bridge Verification

For non-Wormhole bridges:

| # | Check | Status | Location | Notes |
|---|-------|--------|----------|-------|
| 1 | Message source authenticated (signatures/proofs) | YES/NO | {line} | |
| 2 | Source chain ID validated | YES/NO | {line} | |
| 3 | Source contract/address validated | YES/NO | {line} | |
| 4 | Replay protection (nonce/sequence/Table lookup) | YES/NO | {line} | |
| 5 | Message freshness (timestamp check against `timestamp::now_seconds()`) | YES/NO | {line} | |
| 6 | Relayer authorization (if applicable) | YES/NO | {line} | |

---

## Step 3: Timing Window Analysis

### 3a. Finality Asymmetry Model

| Chain | Optimistic Finality | Confirmed Finality | Protocol Assumes |
|-------|--------------------|--------------------|-----------------|
| Aptos | ~1s (BFT commit) | ~1s (BFT - single round) | {which level?} |
| {Remote Chain} | {time} | {time} | {which level?} |
| **Asymmetry Window** | - | - | **{max delay between chains}** |

**Critical question**: When Aptos processes a message about remote chain state, how old can that state be? Compute: `max_staleness = remote_finality + bridge_relay_delay + aptos_processing_time`

### 3b. Stale State Usage Trace

For each piece of state synced cross-chain:

| State Variable | Source Chain | Sync Trigger | Max Staleness | Aptos Functions Using It | Fresh Required? |
|----------------|-------------|-------------|--------------|--------------------------|----------------|
| {state} | {chain} | {event/periodic/manual} | {time estimate} | {list functions} | YES/NO |

For each dependent function on Aptos:
- Is fresh state required or is stale acceptable?
- What decisions are made with potentially stale data?
- Can an attacker exploit the staleness window?

**Aptos-specific**: Check if synced state is stored in a global resource (`move_to`/`borrow_global`) or a `Table`. If a global resource, ALL functions reading it are affected by staleness. If a Table, trace which keys are stale.

### 3c. Aptos-to-Remote Timing Attack

Aptos's fast finality means actions on Aptos are visible almost immediately, but take time to propagate to remote chains:

```
1. Attacker acts on Aptos (visible in ~1s due to BFT finality)
2. Aptos message posted via bridge (begins relay)
3. TIMING WINDOW: Remote chain does not yet know about Aptos action
4. Attacker acts on remote chain using pre-Aptos-action state
5. Bridge message arrives on remote chain - state updates
6. Attacker profited from acting on both chains during asymmetry
```

### 3d. Remote-to-Aptos Timing Attack

```
1. State changes on remote chain (e.g., price moves, governance action)
2. Bridge message relay begins (latency: {estimate})
3. TIMING WINDOW: Aptos still uses old remote state
4. Attacker acts on Aptos using stale remote state
5. Bridge message arrives on Aptos - state updates
6. Attacker profited from Aptos action with stale state
```

---

## Step 4: Resource Creation Requirements

Cross-chain operations on Aptos have unique resource requirements due to Move's ownership model:

| # | Check | Status | Notes |
|---|-------|--------|-------|
| 1 | Recipient `CoinStore<CoinType>` registered before transfer arrival? | YES/NO | If NO: who registers it? Who pays gas? |
| 2 | `coin::register<CoinType>` called for recipient? | YES/NO | If NO: transfer aborts with `ECOIN_STORE_NOT_PUBLISHED` |
| 3 | What happens if recipient has not registered the coin type? | {abort/skip/queue} | Aborted transfers may be lost if no recovery path |
| 4 | Are wrapped coin types (`WrappedCoin<T>`) registered before first bridge transfer? | YES/NO | First bridged token of a type requires coin creation + registration |
| 5 | Are resources created for cross-chain escrow (`move_to`)? | YES/NO | Check signer requirements - does the bridge module have the correct signer capability? |
| 6 | Is there a recovery mechanism for failed deliveries? | YES/NO | Lost funds if no recovery |
| 7 | Can an attacker front-run resource creation with a malicious resource? | YES/NO | Move type system prevents this for same types, but check wrapper types |

**Critical Aptos pattern**: Cross-chain token transfers require the destination account to have a `CoinStore<T>` registered for the specific coin type. If it does not:
- The transfer transaction aborts - tokens may be stuck on the source chain
- Some bridges auto-register (requires signer capability or resource account)
- Some bridges queue the transfer for later claim (is the queue bounded? Who can claim?)

**Resource account pattern**: Many Aptos bridge modules use resource accounts (`account::create_resource_account`) for escrow. Verify:
- The resource account seed is deterministic and collision-free per message
- The resource account signer capability is stored securely (not extractable)
- Resource account creation cannot be front-run by an attacker

---

## Step 5: Nonce and Sequence Management

| # | Check | Status | Location | Notes |
|---|-------|--------|----------|-------|
| 1 | Replay protection exists | YES/NO | {line} | Method: {Table lookup/counter/EventHandle sequence/resource per message} |
| 2 | Replay check is BEFORE state changes | YES/NO | {line} | If after: partial replay possible |
| 3 | Out-of-order messages handled | YES/NO | {line} | Strict ordering vs any-order processing |
| 4 | Sequence gaps handled | YES/NO | {line} | What if message N+1 arrives before N? |
| 5 | Table storage sized for growth | YES/NO | {line} | `Table<u64, bool>` grows unboundedly - gas cost implications |
| 6 | Double-spend across chains | YES/NO | {line} | Same asset spent on both chains during relay |

**Aptos replay patterns**:
- **Table per message**: Store processed sequences in `Table<u64, bool>` or `Table<vector<u8>, bool>`. If key exists, already processed. Reliable but `Table` lookups have gas cost proportional to depth.
- **Counter**: Only process sequence N if N-1 was processed. Enforces ordering but blocks on gaps.
- **Resource per message**: Create a unique resource per processed message hash. Existence check prevents replay. Creates many resources (storage cost).
- **EventHandle sequence**: Use `event::counter` on an EventHandle as implicit sequence. Not reliable for replay - events are not queryable on-chain.

**Move-specific concern**: `Table` entries cannot be iterated or enumerated on-chain. If replay state is in a Table, ensure the lookup key is deterministic from message content (not relayer-supplied).

---

## Step 6: Cross-Chain Price Relay Audit

If oracle prices are relayed cross-chain:

| # | Check | Status | Notes |
|---|-------|--------|-------|
| 1 | Price freshness validated on Aptos side (`timestamp::now_seconds() - price_timestamp < MAX_STALENESS`) | YES/NO | Max acceptable age? |
| 2 | Price source authenticated | YES/NO | Can fake price be relayed? |
| 3 | Price deviation bounds | YES/NO | Max delta from last known price? |
| 4 | Fallback if relay is delayed/offline | YES/NO | What happens to price-dependent operations? |
| 5 | Flash loan on source chain can manipulate relayed price | YES/NO | Is source chain price spot or TWAP? |

**Staleness calculation**: `relay_staleness = source_price_age + bridge_latency + aptos_processing`

If `relay_staleness > acceptable_threshold` at worst case, price is stale. Apply Rule 16 (Oracle Integrity).

**Aptos-specific**: `timestamp::now_seconds()` returns seconds (not milliseconds). Ensure staleness comparisons use consistent units. Also verify `timestamp::now_microseconds()` is not confused with `now_seconds()` - a 1000x unit mismatch could make staleness checks ineffective.

---

## Step 7: Quantify Arbitrage Viability

```
1. Attacker monitors {SOURCE_CHAIN} for state changes at {MONITOR_POINT}
2. State change triggers sync message (latency window opens: {LATENCY_ESTIMATE})
3. Attacker executes on Aptos at {EXPLOIT_FUNCTION} using stale {STALE_STATE}
   -- Aptos execution is near-instant (~1s), so attacker can react quickly
4. Sync message arrives on Aptos, state updates in resource
5. Profit = {PROFIT_FORMULA}
6. Cost = bridge_fees + Aptos_gas + capital_lockup_cost
7. Viable if: profit > cost AND repeatable
```

**Reverse direction** (Aptos -> remote chain):
```
1. Attacker monitors Aptos state change (near-instant visibility due to BFT finality)
2. Attacker front-runs the bridge message on remote chain (longer finality window)
3. Attacker exploits stale state on remote chain before sync arrives
```

**Aptos cost model**: Aptos gas costs are low (~0.001 APT per tx). The primary cost is capital lockup and bridge fees, not gas. This makes small-margin attacks more viable on Aptos than EVM.

---

## Key Questions (must answer all)

1. What is the realistic sync latency for {BRIDGE_PROTOCOL}? (cite documentation)
2. Can an attacker monitor the remote chain and front-run sync on Aptos? (Aptos's low fees make this cheap)
3. What is the maximum state change during normal operation within the sync window?
4. Is this attack repeatable or one-time?
5. Are recipient CoinStores registered before cross-chain transfer arrival?
6. Is replay protection complete (covers all message types, all chains)?
7. Can an attacker exploit Aptos's fast finality to act before the remote chain sees Aptos state?
8. Are cross-chain prices validated for freshness AND deviation bounds?
9. Does VAA/message verification check BOTH source chain AND source address?

---

## Common False Positives

- **Monotonic state**: If synced state only increases, arbitrage may not be profitable in both directions
- **Negligible delta**: If max delta during sync window is <0.1%, may not be economically viable after bridge fees
- **Rate limiting**: If operations have cooldowns longer than sync latency, window may not be exploitable
- **Move type safety**: Move's resource type system prevents fake VAA/message resource injection from incorrect modules - but still verify the module address is correct
- **Bridge-level protections**: Some bridges (Wormhole) have rate limiting or value caps that bound exploitation
- **Freshness enforcement**: If protocol requires `timestamp::now_seconds() - last_sync < MAX_STALENESS`, stale state is rejected

---

## Instantiation Parameters
```
{CONTRACTS}           -- Modules to analyze
{BRIDGE_PROTOCOL}     -- Specific bridge (Wormhole, LayerZero, CCIP, custom)
{SYNC_POINT}          -- Function where cross-chain state is consumed
{DEPENDENT_FUNCTIONS} -- Functions that read synced state
{SOURCE_CHAIN}        -- Chain where state originates
{DEST_CHAIN}          -- Chain where stale state is exploited
{MONITOR_POINT}       -- What attacker monitors on source chain
{EXPLOIT_FUNCTION}    -- Function attacker calls on dest chain
{STALE_STATE}         -- Specific state variable/resource field that becomes stale
{LATENCY_ESTIMATE}    -- Realistic bridge latency
{PROFIT_FORMULA}      -- (new_value - old_value) * position_size
```

---

## Output Schema

| Field | Required | Description |
|-------|----------|-------------|
| bridge_inventory | yes | All cross-chain messaging infrastructure |
| verification_audit | yes | VAA/message verification completeness |
| timing_windows | yes | Asymmetry windows with duration estimates |
| resource_creation | yes | Recipient resource requirements and failure modes |
| replay_protection | yes | Nonce/sequence management assessment |
| price_relay_audit | if applicable | Cross-chain price freshness and manipulation risk |
| arbitrage_viability | yes | Quantified attack profitability or NOT_VIABLE |
| finding | yes | CONFIRMED / REFUTED / CONTESTED |
| evidence | yes | Code locations with line numbers |
| step_execution | yes | Status for each step |

---

### Denylist Enforcement Lag
- **Denylist enforcement lag**: For cross-chain denylist/blocklist updates, check the window between message receipt and enforcement. Can transactions from denylisted addresses execute during this window? Are in-flight operations for denylisted addresses cancelled or allowed to complete?

---

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Identify Cross-Chain Messaging Infrastructure | YES | | |
| 2. Cross-Chain Message Verification Audit | YES | | |
| 3. Timing Window Analysis (both directions) | YES | | |
| 4. Resource Creation Requirements | YES | | |
| 5. Nonce and Sequence Management | YES | | |
| 6. Cross-Chain Price Relay Audit | IF price relay detected | | |
| 7. Quantify Arbitrage Viability | YES | | |

### Cross-Reference Markers

**After Step 2**: If VAA verification is incomplete -> immediate finding, do not wait for timing analysis.

**After Step 3**: Feed timing windows to TEMPORAL_PARAMETER_STALENESS skill for parameters cached across chain boundaries.

**After Step 4**: If resource creation can fail -> cross-reference with REF_LIFECYCLE skill for stranded asset analysis.

**After Step 6**: Feed price staleness findings to ORACLE_ANALYSIS (Aptos version) if applicable.

## references/aptos/dependency-audit.md

---
name: "dependency-audit"
description: "Trigger EXTERNAL_LIB flag detected (protocol uses third-party Move dependencies) - Used by Breadth agents, depth-external"
---

# Skill: DEPENDENCY_AUDIT

> **Trigger**: EXTERNAL_LIB flag detected (protocol uses third-party Move dependencies)
> **Used by**: Breadth agents, depth-external
> **Covers**: Third-party library security, upgrade policy risks, critical function correctness, transitive dependency chains

## Purpose

Audit third-party Move dependencies for security risks. Aptos protocols commonly depend on external math libraries, utility modules, and protocol SDKs. Unlike EVM (where dependencies are compiled into the contract), Move dependencies are on-chain modules that can be independently upgraded. A dependency upgrade can silently change the behavior of the audited protocol.

## Methodology

### STEP 1: Dependency Inventory

Parse `Move.toml` for all dependencies. Categorize each:

| # | Dependency | Source | Category | Upgrade Policy | Revision Pinned? |
|---|-----------|--------|----------|---------------|-----------------|
| 1 | AptosFramework | aptos-framework repo | FRAMEWORK | Framework governance | {rev hash or branch} |
| 2 | AptosStd | aptos-framework repo | FRAMEWORK | Framework governance | {rev hash or branch} |
| 3 | AptosToken | aptos-framework repo | FRAMEWORK | Framework governance | {rev hash or branch} |
| 4 | {third_party_lib} | {git URL} | THIRD_PARTY | {compatible/immutable/unknown} | {YES: rev=abc123 / NO: branch=main} |
| 5 | {sub_module} | local path | IN_SCOPE | N/A (part of audit) | N/A |

**Categories**:
- **FRAMEWORK**: `aptos_framework`, `aptos_std`, `aptos_token`, `aptos_token_objects` - trusted, framework-governance-controlled. Audit framework USAGE, not framework internals.
- **THIRD_PARTY**: External libraries (math utils, oracle SDKs, DEX interfaces). MUST audit all called functions.
- **IN_SCOPE**: Protocol's own sub-modules. Fully in scope.

**MANDATORY PARSE**: Read `Move.toml` (and any sub-package `Move.toml` files) for:
1. `[dependencies]` section entries
2. `git = "..."` URLs - identify the source repository
3. `rev = "..."` - pinned revision hash (safe) vs `branch = "main"` (dangerous)
4. `local = "..."` - in-scope sub-modules

### STEP 2: Upgrade Policy Risk Assessment

For each THIRD_PARTY dependency:

| Dependency | On-Chain Address | Upgrade Policy | Can Upgrade Without Protocol Knowledge? | Risk Level |
|-----------|-----------------|---------------|----------------------------------------|-----------|
| {lib} | {0x...} | immutable | NO | LOW |
| {lib} | {0x...} | compatible | YES - publisher can add functions, change logic | HIGH |
| {lib} | {0x...} | unknown | VERIFY ON-CHAIN | ASSESS |

**Check for each `compatible` dependency**:
1. Can the dependency publisher add new friend declarations (giving new modules access to internal state)?
2. Can the dependency publisher change function implementations (same signature, different logic)?
3. Can the dependency publisher add new public functions that interact with stored state?
4. Does the audited protocol store any state that the dependency module can access?
5. Is there a governance/multisig controlling the dependency's publisher address?

**Severity**: If a `compatible` third-party dependency can be upgraded to change behavior of functions the protocol calls, AND the protocol has no way to detect or prevent this -> minimum MEDIUM finding.

**Pinning check**: If `Move.toml` uses `branch = "main"` instead of `rev = "abc123"`:
- Build reproducibility is broken
- Developer may unknowingly compile against a different version
- Document as INFO finding (build hygiene)

### STEP 3: Critical Function Audit

For each function called from a THIRD_PARTY dependency:

#### 3a. Function Inventory

| # | Called Function | From Module | Parameters | Return Type | Frequency | Impact If Wrong |
|---|---------------|-------------|-----------|-------------|-----------|----------------|
| 1 | {lib::func()} | {our_module} | {params} | {return} | {every tx / periodic / init only} | {describe} |

#### 3b. Correctness Verification

For each critical function (called frequently OR high impact if wrong):

**Overflow/underflow check**:
1. Does the function handle multiplication overflow? (e.g., `a * b` where both are u64 - can overflow)
2. Does it handle division by zero?
3. Does it use intermediate u128 for precision in u64 arithmetic?
4. **Bit shift safety**: Does it use `<<` or `>>`? If so, is the shift amount bounded to < 64 (for u64) or < 128 (for u128)? Unbounded bit shifts are a known attack vector (historical exploit: bit shift overflow in a custom shift helper allowed minting tokens from minimal liquidity).

**Edge case check**:
| Input | Expected Output | Actual Output | Correct? |
|-------|----------------|---------------|----------|
| 0 | {expected} | {verify} | YES/NO |
| 1 | {expected} | {verify} | YES/NO |
| MAX_U64 | {expected: revert or handled} | {verify} | YES/NO |
| MAX_U128 | {expected} | {verify} | YES/NO |

**Specification check**:
- Does the function have documented behavior? (comments, spec blocks)
- Does the implementation match the specification?
- If the function is a math operation: verify against a reference implementation or mathematical formula

#### 3c. Trust Boundary Analysis

For each third-party function call:

| Call | Trusts Dependency To | What If Dependency Lies/Breaks | Detection? |
|-----|---------------------|-------------------------------|-----------|
| {lib::get_price()} | Return accurate price | Protocol uses wrong price → fund loss | {sanity check present?} |
| {lib::sqrt(x)} | Return correct sqrt | Wrong math → accounting error | {no detection} |

**Check**: Does the protocol validate the RETURN VALUE of third-party calls? Or does it blindly trust the result?

If no validation AND high impact -> FINDING.

### STEP 4: Transitive Dependency Analysis

Check whether third-party dependencies have their own dependencies:

#### 4a. Dependency Tree

```
Protocol
├── aptos_framework (FRAMEWORK)
├── third_party_lib_A
│   ├── aptos_framework (FRAMEWORK - OK, shared)
│   └── third_party_lib_B (THIRD_PARTY - audit this!)
│       └── aptos_std (FRAMEWORK - OK)
└── third_party_lib_C
    └── (no additional deps)
```

#### 4b. Transitive Risk Assessment

| Transitive Dependency | Reached Via | Upgrade Policy | Audited? | Risk |
|-----------------------|-----------|---------------|---------|------|
| {lib_B} | lib_A -> lib_B | {policy} | YES/NO | {assess} |

**Check**:
1. Are ALL transitive dependencies pinned to specific revisions?
2. Can a transitive dependency be upgraded independently, changing the behavior of the direct dependency?
3. Are there version conflicts (two dependencies requiring different versions of the same module)?

## Key Questions (Must Answer All)

1. **Pinning**: Are all third-party dependencies pinned to specific git revisions?
2. **Upgrade risk**: Can any dependency be upgraded without the protocol's knowledge?
3. **Math safety**: Do all third-party math functions handle overflow, zero, and boundary inputs correctly?
4. **Bit shift safety**: Are all bit shift operations bounded? (Critical after Cetus exploit)
5. **Trust validation**: Does the protocol validate return values from third-party calls?
6. **Transitive exposure**: Are there unaudited transitive dependencies?

## Common False Positives

1. **Framework dependencies**: `aptos_framework`, `aptos_std`, `aptos_token` are framework-governed and heavily audited - do not flag as third-party risk (but DO audit usage patterns)
2. **Immutable dependencies**: If the on-chain module is published with `immutable` policy, upgrade risk is zero
3. **Pinned to audited revision**: If the dependency is pinned to a specific, known-audited revision, transitive upgrade risk is build-time only (not runtime)
4. **Standard math operations**: Framework-provided `math64::mul_div()` and similar are well-tested - focus audit on third-party math libraries

## Output Schema

```markdown
## Finding [DEP-N]: Title

**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: ✓1,2,3,4 | ✗N(reason) | ?N(uncertain)
**Rules Applied**: [R1:✓/✗, R4:✓/✗, R8:✓/✗, R10:✓/✗]
**Severity**: Critical/High/Medium/Low/Info
**Location**: Move.toml or module_name.move:LineN

**Dependency**: {name and source}
**Risk Type**: UPGRADE_RISK / MATH_ERROR / TRUST_BOUNDARY / TRANSITIVE_EXPOSURE
**Upgrade Policy**: {immutable/compatible/unknown}

**Description**: What's wrong
**Impact**: What can happen (silent behavior change, math error, fund loss)
**Evidence**: Code showing the dependency usage and risk

### Precondition Analysis (if PARTIAL/REFUTED)
**Missing Precondition**: [What blocks exploitation]
**Precondition Type**: STATE / ACCESS / TIMING / EXTERNAL / BALANCE

### Postcondition Analysis (if CONFIRMED/PARTIAL)
**Postconditions Created**: [What conditions this creates]
**Postcondition Types**: [List applicable types]
**Who Benefits**: [Who can use these]
```

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Dependency Inventory | YES | ✓/✗/? | All Move.toml entries parsed and categorized |
| 2. Upgrade Policy Risk | FOR EACH third-party dep | ✓/✗/? | On-chain policy verified |
| 3a. Function Inventory | YES | ✓/✗/? | All called functions from third-party listed |
| 3b. Correctness Verification | FOR EACH critical function | ✓/✗/? | Overflow, zero, MAX tested |
| 3c. Trust Boundary Analysis | YES | ✓/✗/? | Return value validation checked |
| 4. Transitive Dependency Analysis | IF transitive deps exist | ✓/✗(N/A)/? | Full tree mapped |

If any step skipped, document valid reason (N/A, no third-party deps, all deps immutable).

## references/aptos/economic-design-audit.md

---
name: "economic-design-audit"
description: "Trigger Pattern MONETARY_PARAMETER flag (required) - Inject Into Breadth agents (merged via M4 hierarchy)"
---

# ECONOMIC_DESIGN_AUDIT Skill

> **Trigger Pattern**: MONETARY_PARAMETER flag (required)
> **Inject Into**: Breadth agents (merged via M4 hierarchy)
> **Purpose**: Analyze admin-settable economic parameters (fees, rates, thresholds, emission schedules) for boundary violations, invariant breaks, interaction extremes, and fee formula correctness

For every monetary parameter setter (rate, rebase, supply, mint, burn, emission, inflation,
peg, price cap/floor, fee, reward rate) in the protocol:

## 1. Parameter Boundary Analysis

| Parameter | Setter Function | Min Value | Max Value | Enforced? | Impact at Min | Impact at Max |
|-----------|----------------|-----------|-----------|-----------|---------------|---------------|
| {param} | {set_fn} | {min} | {max} | YES/NO | {impact} | {impact} |

For each parameter: substitute min and max into ALL consuming functions.
Tag: [BOUNDARY:param=val -> outcome]

**Aptos-specific checks**:
- Are bounds enforced via `assert!()` in the setter? If not, admin can set any value within the type range (`u64::MAX`, `u128::MAX`)
- Does the protocol use `u64` or `u128` for monetary values? Check for overflow at max values.
- Are there separate bounds for testnet vs mainnet? (Sometimes hardcoded differently)

## 2. Economic Invariant Identification

List all economic invariants the protocol must maintain:

| Invariant | Parameters Involved | Can Admin Break It? | Functions That Assume It |
|-----------|-------------------|--------------------|-----------------------|
| total_supply == sum(all_balances) | mint/burn params | YES/NO | {fn list} |
| fees < principal | fee_rate | YES/NO | {fn list} |
| collateral_ratio >= min_ratio | ratio_param | YES/NO | {fn list} |
| rewards_distributed <= rewards_pool | emission_rate | YES/NO | {fn list} |

For each setter: can changing this parameter break an invariant that user-facing
functions depend on? If yes -> finding.

**Aptos-specific invariants**:
- `FungibleAsset` total supply tracking via `supply()` must match minted - burned
- Object-based accounting: sum of all store balances == total assets managed
- Resource conservation: tokens entering protocol == tokens accounted internally

## 3. Rate/Supply Interaction Matrix

For protocols with multiple monetary parameters that interact:

| Parameter A | Parameter B | Interaction | Can A*B Produce Extreme Output? |
|-------------|-------------|-------------|-------------------------------|
| {param_a} | {param_b} | {relationship} | YES/NO: {at what values} |

Check: can two independently-valid parameter settings combine to create an
extreme or invalid economic state? (Rule 14 constraint coherence)

**Examples**:
- Fee rate A = 50% AND fee rate B = 50% -> combined 75% fee (not 100%, because B applies to post-A amount)
- Reward rate = max AND lock period = min -> excessive reward extraction
- Borrow rate = max AND liquidation threshold lowered -> cascade liquidations

## 4. Fee Formula Verification at Normal Values

For every fee-related computation (fee calculation, fee deduction, fee distribution):

### 4a. Concrete Example Computation

Pick 3 representative fee rates (e.g., 1% = 100 BPS, 5% = 500 BPS, 10% = 1000 BPS) and trace through the actual code formula:

| Fee Param | Value | Formula | Input Amount | Expected Output | Actual Output | Match? |
|-----------|-------|---------|-------------|----------------|---------------|--------|
| {fee_bps} | 100 | {code formula} | 1_000_000_00 (1e8) | {expected} | {computed} | YES/NO |
| {fee_bps} | 500 | {code formula} | 1_000_000_00 (1e8) | {expected} | {computed} | YES/NO |
| {fee_bps} | 1000 | {code formula} | 1_000_000_00 (1e8) | {expected} | {computed} | YES/NO |

Tag: `[BOUNDARY:fee_bps={val} -> effective_rate={computed_rate}]`

**Red flags**:
- Gross-up formulas: `amount * MAX / (MAX - fee)` charges effective rate of `fee/(MAX-fee)`, not `fee/MAX`. At 5% this is 5.26%, not 5%. Document whether this is intentional.
- Fee-on-fee: Does fee A's output feed into fee B's input? If so, the combined effective rate is not simply A + B.
- Rounding direction: In Move integer math, division truncates. `amount * fee / 10000` always rounds DOWN (favoring user). Check if protocol uses `(amount * fee + 9999) / 10000` for ceiling (favoring protocol).
- Precision loss: With `u64` at 1e8 scale (Aptos standard), do intermediate products overflow? `u64::MAX = 18.4e18`, so `amount * fee` overflows if both are large. Check for `u128` intermediate or `math::mul_div` usage.

### 4b. Fee Interaction Matrix

For protocols with multiple fee types:

| Fee A | Fee B | A Output Feeds B Input? | Combined Effective Rate | Independent Rate Sum | Discrepancy? |
|-------|-------|------------------------|------------------------|---------------------|-------------|

### 4c. Fee Impact on Share Price

If the protocol uses share-based accounting (vaults, LP tokens):
- After fee deduction: does the share price change?
- Does the fee mechanism create a spread between deposit and immediate withdrawal?
- Is the spread documented and within reasonable bounds?

### 4d. Fee-Base Consistency

For every fee computation, trace the base amount (the value the fee is computed on) through ALL subsequent code paths:

| Fee Site | Base Amount Variable | Modified After Fee? | Modified How | Fee Recomputed? | Overcharge? |
|----------|---------------------|--------------------:|-------------|-----------------|-------------|

**Methodology**:
- Identify the variable used as fee base (e.g., `amount`, `deposit_amount`)
- Trace that variable FORWARD from the fee computation to the end of the function
- If the variable is reduced (capped, downscaled, adjusted to remaining capacity, slippage-adjusted) AFTER the fee was computed -> the fee was charged on a larger base than what was actually used
- **Concrete test**: If `fee = amount * fee_rate / MAX`, then `amount` is reduced to `leftover` (e.g., remaining allocation), the user paid `fee` on `amount` but only `leftover` was processed -- overcharge of `fee * (1 - leftover/amount)`

## 5. Emission/Inflation Sustainability

For protocols with emission/inflation/rebase mechanics:

| Check | Value | Sustainable? | Impact if Unsustainable |
|-------|-------|-------------|----------------------|
| Max emission rate per day | {amount} | YES/NO | {impact} |
| Max emission rate per year | {amount} | YES/NO | {impact} |
| Supply cap exists? | YES/NO | N/A | {impact if no cap} |
| Can cap be bypassed by param changes? | YES/NO | N/A | {how} |
| Reward pool sufficient for emission schedule? | YES/NO | N/A | {what happens when depleted} |

**Aptos-specific emission checks**:
- Does the module use `timestamp::now_seconds()` for emission calculations? Verify time-based math is correct.
- Are emissions denominated in the correct decimal scale (1e8 for most Aptos tokens)?
- Can emission rate be set to drain reward pool in a single epoch/transaction?

## Instantiation Parameters
```
{CONTRACTS}              -- Move modules to analyze
{MONETARY_PARAMS}        -- Admin-settable economic parameters
{FEE_FUNCTIONS}          -- Functions containing fee calculations
{INVARIANTS}             -- Expected economic invariants
{EMISSION_MECHANICS}     -- Emission/inflation/rebase mechanics (if any)
```

## Finding Template

```markdown
**ID**: [ED-N]
**Severity**: [based on fund impact at boundary/extreme values]
**Step Execution**: checkmark1,2,3,4,5 | x(reasons) | ?(uncertain)
**Rules Applied**: [R10:Y, R14:Y]
**Location**: module::function:LineN
**Title**: [Parameter/invariant/fee] issue in [function] enables [attack/failure]
**Description**: [Specific economic design issue with concrete boundary values]
**Impact**: [Quantified impact at boundary conditions]
```

## Output Schema

| Field | Required | Description |
|-------|----------|-------------|
| parameter_boundaries | yes | All monetary parameters with min/max analysis |
| invariants | yes | Economic invariants and whether they can break |
| fee_verification | yes | Fee formula verification at normal values |
| interaction_matrix | yes | Parameter interaction analysis |
| finding | yes | CONFIRMED / REFUTED / CONTESTED |
| evidence | yes | Code locations with line numbers |
| step_execution | yes | Status for each step |

---

## Step Execution Checklist (MANDATORY)

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Parameter Boundary Analysis | YES | Y/N/? | |
| 2. Economic Invariant Identification | YES | Y/N/? | |
| 3. Rate/Supply Interaction Matrix | IF >1 monetary param | Y/N(N/A)/? | |
| 4a. Fee Formula Verification (concrete examples) | IF fee parameters detected | Y/N(N/A)/? | |
| 4b. Fee Interaction Matrix | IF multiple fee types | Y/N(N/A)/? | |
| 4c. Fee Impact on Share Price | IF share-based accounting | Y/N(N/A)/? | |
| 4d. Fee-Base Consistency | IF fee parameters detected | Y/N(N/A)/? | |
| 5. Emission/Inflation Sustainability | IF emission/rebase detected | Y/N(N/A)/? | |

## references/aptos/external-precondition-audit.md

---
name: "external-precondition-audit"
description: "Trigger Pattern Any external module interaction detected in attack_surface.md - Inject Into Breadth agents (merged via M5 hierarchy)"
---

# EXTERNAL_PRECONDITION_AUDIT Skill

> **Trigger Pattern**: Any external module interaction detected in attack_surface.md
> **Inject Into**: Breadth agents (merged via M5 hierarchy)
> **Constraint**: Interface-level inference only -- no production fetch required

For every external module the protocol interacts with:

## 1. Interface-Level Requirement Inference

From the `use` imports and function calls to external modules, infer what the external module requires:

| External Function Called | Module::Function | Parameters Passed | Likely Preconditions (from signature + abort codes) | Our Protocol Validates? |
|--------------------------|-----------------|-------------------|-----------------------------------------------------|------------------------|

**Inference method**: Read the function signature, type parameters, ability constraints, and abort conditions. Example: `coin::withdraw<CoinType>(account: &signer, amount: u64)` -> infer that `account` must have sufficient balance, `CoinType` must be initialized, amount must be > 0. Check abort codes in framework source if available.

**Aptos-specific patterns**:
- `&signer` parameters: does external module require the signer to own a specific resource?
- Generic type parameters `<T>`: does external module require `T` to be registered/initialized?
- `Object<T>` parameters: does external module validate object ownership or type?
- Abort conditions: enumerate all `assert!` / `abort` in external function that could revert our call

## 2. Return Value Consumption

| External Call | Return Type | How Protocol Uses Return | Failure Mode if Return Unexpected |
|--------------|-------------|-------------------------|----------------------------------|

For each return value:
- What happens if it returns 0? What happens if it returns `MAX_U64`?
- What happens if the external call aborts?
- For `Option<T>` returns: does our protocol handle `none` correctly?
- For `FungibleAsset` returns: is metadata validated after receiving?
- For `Object<T>` returns: is the object type verified before use?

## 3. State Dependency Mapping

| Protocol State | Depends on External State | External Module Upgradeable? | State Can Change Without Our Knowledge? |
|---------------|--------------------------|-----------------------------|-----------------------------------------|

For each dependency:
- **Upgrade risk**: Aptos modules are upgradeable by default (`compatible` policy). Can the external module add new abort conditions to a function we call? Can it change return value semantics within compatible upgrade bounds?
- **Immutability check**: Is the external module published as `immutable`? If so, behavior is frozen.
- **State mutation timing**: Can external module state change between our module's read and use within the same transaction? (e.g., another instruction in a multi-instruction transaction modifies external state)
- **Framework dependency**: If depending on `aptos_framework` modules, are there governance-controlled parameters that could change? (e.g., `transaction_fee`, `staking_config`)

## Instantiation Parameters

```
{CONTRACTS}           -- List of modules to analyze
{EXTERNAL_MODULES}    -- External modules identified during recon
{FRAMEWORK_DEPS}      -- aptos_framework / aptos_std / aptos_token dependencies
```

## Output Schema

For each finding:

```markdown
## Finding [EP-N]: Title

**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: S1,S2,S3 | X(reasons) | ?(uncertain)
**Rules Applied**: [R1:Y, R4:Y, R8:Y]
**Severity**: Critical/High/Medium/Low/Info
**Location**: module::function (source_file.move:LineN)

**External Dependency**: {module::function}
**Failure Mode**: {what breaks}

**Description**: What's wrong
**Impact**: What can happen (abort DoS, wrong state, fund loss)
**Evidence**: Code showing dependency and missing validation
```

## Step Execution Checklist

| Section | Required | Completed? |
|---------|----------|------------|
| 1. Interface-Level Requirement Inference | YES | Y/N/? |
| 2. Return Value Consumption | YES | Y/N/? |
| 3. State Dependency Mapping | YES | Y/N/? |

## references/aptos/flash-loan-interaction.md

---
name: "flash-loan-interaction"
description: "Trigger Pattern FLASH_LOAN flag (required) or BALANCE_DEPENDENT flag (optional complement) - Inject Into Breadth agents, depth-token-flow, depth-edge-case"
---

# FLASH_LOAN_INTERACTION Skill

> **Trigger Pattern**: FLASH_LOAN flag (required) or BALANCE_DEPENDENT flag (optional complement)
> **Inject Into**: Breadth agents, depth-token-flow, depth-edge-case
> **Purpose**: Analyze flash loan attack surfaces in Aptos Move protocols, focusing on the hot potato receipt pattern, state manipulation during flash loan windows, and defense parity

For every flash-loan-accessible state variable or precondition in the protocol:

**STEP PRIORITY**: Steps 5 (Defense Audit) and 5b (Defense Parity) are where HIGH/CRITICAL severity findings most commonly hide. Do NOT rush these steps. If constrained, skip conditional sections (0c, 4) before skipping 5, 5b, or 3d.

## 0. External Flash Susceptibility Check

Before analyzing the protocol's OWN flash loan paths, check whether external protocols the contract interacts with are susceptible to third-party flash manipulation.

### 0a: External Interaction Inventory

| External Protocol | Interaction Type | State Read by Our Protocol | Can 3rd Party Flash-Manipulate That State? |
|-------------------|-----------------|---------------------------|-------------------------------------------|
| {DEX/pool/vault} | {swap/deposit/query} | {reserves, price, balance} | {YES if spot state / NO if TWAP or time-weighted} |

### 0b: Third-Party Flash Attack Modeling

For each external state marked YES in 0a, model:
1. **Before**: Protocol reads external state X (e.g., pool reserves, spot price from AMM)
2. **Flash manipulate**: Attacker flash-borrows and trades on the external protocol to move state X
3. **Victim call**: Attacker calls OUR protocol function that reads manipulated state X
4. **Restore**: Attacker reverses the external manipulation
5. **Impact**: What did the attacker gain from our protocol acting on manipulated state?

**Key question**: Does our protocol use **spot state** (manipulable) or **time-weighted state** (resistant)?

<!-- LOAD_IF: DEX_INTERACTION -->
### 0c: DEX Price Manipulation Cost Estimation

For each external DEX/pool whose spot state is read by the protocol, estimate manipulation cost:

| Pool | Liquidity (USD) | Target Price Change | Est. Trade Size | Slippage Cost | Protocol Extractable Value | Profitable? |
|------|----------------|--------------------:|----------------|--------------|---------------------------|-------------|
| {pool} | {TVL} | {%} | {USD} | {USD} | {USD} | {YES/NO} |

**For Aptos AMMs**: Most use constant-product (xy=k) or stableswap curves. Identify the specific AMM type from the protocol's swap function signatures (weighted pools, stableswap, or standard xy=k).
<!-- END_LOAD_IF: DEX_INTERACTION -->

## 1. Flash-Loan-Accessible State Inventory

Enumerate ALL protocol state that can be manipulated within a single transaction via flash-borrowed capital:

| State Variable / Query | Location | Read By | Write Path | Flash-Accessible? | Manipulation Cost |
|------------------------|----------|---------|------------|-------------------|-------------------|
| `fungible_asset::balance(store)` | {module} | {functions} | Direct deposit to store | YES if store accepts | 0 (unsolicited) |
| `coin::balance<T>(addr)` | {module} | {functions} | Direct `coin::deposit` | YES if CoinStore exists | 0 (unsolicited) |
| Pool reserves | {pool module} | {functions} | Swap on pool | YES | Slippage cost |
| Oracle spot price | {oracle} | {functions} | Trade on source DEX | YES | Market depth |
| Threshold/quorum state | {module} | {functions} | Deposit/stake | YES | Threshold amount |

**Aptos flash loan mechanics (hot potato pattern)**:
- Flash loan providers (Thala, Echelon, etc.) issue a `FlashLoanReceipt` struct with NO abilities (no `copy`, no `drop`, no `store`, no `key`)
- The receipt MUST be consumed by `repay()` in the same transaction -- Move's type system enforces this
- No callback mechanism: caller receives receipt, performs operations, then passes receipt to repay
- The receipt struct often contains the borrowed amount for repayment validation

**For each YES entry**: trace all functions that READ this state and make decisions based on it.

**Rule 15 check**: For each balance/oracle/threshold/rate precondition, model the flash loan atomic sequence.

## 2. Atomic Attack Sequence Modeling

For each flash-loan-accessible state identified in Step 1:

### Attack Template
```
1. BORROW: Flash-borrow {amount} of {CoinType/FA} from {source}
   -> Receive FlashLoanReceipt (hot potato, no abilities)
2. MANIPULATE: {action} to change {state_variable} from {value_before} to {value_after}
3. CALL: Invoke {target_function} which reads manipulated state
4. EXTRACT: {what_is_gained} -- quantify: {amount}
5. RESTORE: {action} to return state (if needed before repayment)
6. REPAY: Call repay() with FlashLoanReceipt + {amount + fee}
7. PROFIT: {extract - fee - gas} = {net_profit}
```

**Profitability gate**: If net_profit <= 0 for all realistic amounts -> document as NON-PROFITABLE but check Step 3 for multi-call chains.

**For each sequence, verify**:
- [ ] Can steps 2-5 execute atomically (same transaction entry function)?
- [ ] Does any step abort under normal conditions?
- [ ] Is the manipulation detectable/preventable by the protocol?
- [ ] What is the minimum flash loan amount needed?
- [ ] Does the hot potato receipt constrain the call sequence? (receipt must be threaded through all calls)

## 3. Cross-Function Flash Loan Chains

Model multi-call atomic sequences within a single flash loan:

| Step | Function Called | State Before | State After | Enables Next Step? |
|------|---------------|-------------|------------|-------------------|
| 1 | {function_A} | {state} | {state'} | YES -- changes {X} |
| 2 | {function_B} | {state'} | {state''} | YES -- enables {Y} |
| N | {function_N} | {state^N} | {final} | EXTRACT profit |

**Key question**: Can calling function A then function B in the same transaction produce a state that neither function alone could create?

**Aptos-specific multi-call patterns**:
- Deposit to pool -> manipulate price via swap -> withdraw at inflated rate
- Flash-stake to meet threshold -> trigger reward calculation -> unstake
- Borrow from protocol A -> manipulate collateral oracle via AMM trade -> liquidate on protocol B -> repay A
- Inflate FungibleStore balance via deposit -> trigger share price recalculation -> withdraw

### 3b. Flash-Loan-Enabled Debounce DoS

For each permissionless function with a cooldown/debounce that affects OTHER users (global cooldown, shared timestamp, epoch-bound action):
Can attacker flash-borrow -> call debounced function -> trigger cooldown, blocking legitimate callers?

| Function | Cooldown Scope | Shared Across Users? | Flash-Triggerable? | DoS Duration |
|----------|---------------|---------------------|-------------------|-------------|

If cooldown is global/shared AND function is permissionless AND flash-triggerable -> FINDING (R2, minimum Medium).

### 3c. No-Op Resource Consumption

For each state-modifying function with a limited-use resource (cooldown, one-time flag, nonce, epoch-bound action):
Can it be called with parameters producing zero economic effect (amount=0, same-token swap, self-transfer) while consuming the resource?

| Function | Resource Consumed | No-Op Parameters | Resource Wasted? | Impact |
|----------|------------------|-----------------|-----------------|--------|

If a no-op call consumes a resource blocking legitimate use -> FINDING (R2, resource waste).

### 3d. External Flash x Debounce Cross-Reference (MANDATORY)

For EACH external protocol flagged as flash-susceptible in Section 0:

| External Protocol | Flash-Accessible Action | Debounce/Cooldown Affected (from 3b) | Combined Severity |
|-------------------|------------------------|--------------------------------------|-------------------|

Cross-reference: Can the external flash loan trigger ANY debounce/cooldown found in Step 3b?
If YES:
1. Is the debounce consumption **permanent** (no admin reset) or **temporary** (auto-expires)?
2. If permanent: is there ANY on-chain path to reset? (admin function, governance, time-based expiry)
3. Combined finding inherits the HIGHER severity of the two individual findings
4. Tag: `[TRACE:flash({external}) -> call({debounce_fn}) -> cooldown consumed -> {duration/permanent}]`

If no debounce functions exist from 3b: mark N/A and skip.

<!-- LOAD_IF: BALANCE_DEPENDENT -->
## 4. Flash Loan + Donation Compound Attacks

Combine flash loan capital with unsolicited token transfers:

| Donation Target | Flash Loan Action | Combined Effect | Profitable? |
|-----------------|-------------------|-----------------|-------------|
| FungibleStore balance | Deposit/withdraw | Rate manipulation | {YES/NO} |
| CoinStore<T> balance | Swap on DEX pool | Price oracle manipulation | {YES/NO} |
| Governance token balance | Vote/propose | Quorum manipulation | {YES/NO} |

**Aptos-specific donation vectors**:
- `primary_fungible_store::deposit()` -- can deposit to any address's primary store if the store exists
- `coin::deposit<T>()` -- can deposit to any address with a registered CoinStore<T>
- Direct `fungible_asset::deposit()` with a FungibleStore reference
- Object-based stores may have different deposit access patterns

**Check**: Can a flash-borrowed amount be deposited (not through protocol's deposit logic) to the protocol's FungibleStore to manipulate `balance()` accounting, and then extracted via a subsequent protocol call within the same transaction?
<!-- END_LOAD_IF: BALANCE_DEPENDENT -->

## 5. Flash Loan Defense Audit

For each flash-loan-accessible attack path identified:

| Defense | Present? | Effective? | Bypass? |
|---------|----------|------------|---------|
| Reentrancy guard (Move has no native) | YES/NO | {analysis} | {if YES: how} |
| Same-transaction detection (custom) | YES/NO | {analysis} | {bypass vector?} |
| TWAP instead of spot price | YES/NO | TWAP window length: {N} | Short TWAP vulnerable? |
| Minimum lock period / cooldown | YES/NO | Duration: {N seconds/epochs} | Bypass via partial? |
| Balance snapshot (before/after comparison) | YES/NO | {analysis} | {if YES: how} |
| Flash loan fee exceeds profit | YES/NO | Fee: {X}, max profit: {Y} | Fee < profit? |
| Hot potato receipt threading requirement | YES/NO | Receipt must flow through {path} | Can bypass receipt checks? |

**Aptos-specific defense notes**:
- Move does NOT have native reentrancy guards (no `nonReentrant` modifier)
- Move's borrow checker prevents some reentrancy patterns at compile time (cannot borrow `&mut` twice)
- However, inter-module calls can create reentrancy-like patterns via public functions
- Hot potato pattern enforces same-transaction completion but does NOT prevent state manipulation between borrow and repay
- `timestamp::now_seconds()` granularity is per-second, not per-block -- same-second detection is unreliable

## 5b. Defense Parity Audit (Cross-Module)

For each user-facing action that exists in multiple modules or paths (stake, withdraw, claim, swap):

| Action | Module A | Flash Defense | Module B | Flash Defense | Parity? |
|--------|----------|---------------|----------|---------------|---------|
| {action} | {module} | {defense list} | {module} | {defense list} | {GAP if different} |

**Key question**: If ModuleA::stake() has a cooldown that prevents flash-stake-claim-withdraw,
but ModuleB::stake() has NO cooldown for the same economic action -- can an attacker use
ModuleB as the undefended path to extract the same value?

For each GAP found:
1. Can the undefended module be used to achieve the same economic outcome?
2. Does the defended module's protection become meaningless if the undefended path exists?
3. Is the defense difference intentional (documented via friend declarations) or accidental?

## Instantiation Parameters
```
{CONTRACTS}              -- Move modules to analyze
{FLASH_LOAN_SOURCES}     -- Flash loan providers (Thala, Echelon, custom)
{RECEIPT_STRUCTS}         -- Hot potato receipt struct definitions
{FLASH_ACCESSIBLE_STATE} -- State variables manipulable via flash-borrowed capital
{EXTERNAL_PROTOCOLS}     -- External protocols whose state the contract reads
```

## Finding Template

```markdown
**ID**: [FL-N]
**Severity**: [based on profitability and fund impact]
**Step Execution**: checkmark1,2,3,4,5 | x(reasons) | ?(uncertain)
**Rules Applied**: [R2:Y, R4:Y, R10:Y, R15:Y]
**Location**: module::function:LineN
**Title**: Flash loan enables [manipulation] via [mechanism]
**Description**: [Full atomic attack sequence with amounts]
**Impact**: [Quantified profit/loss with realistic flash loan amounts]
```

## Output Schema

| Field | Required | Description |
|-------|----------|-------------|
| external_susceptibility | yes | External protocols susceptible to flash manipulation |
| flash_accessible_state | yes | All state manipulable within a transaction |
| attack_sequences | yes | Modeled atomic attack sequences with profitability |
| cross_function_chains | yes | Multi-call chains within flash loan window |
| defense_audit | yes | Defenses present and their effectiveness |
| defense_parity | yes | Cross-module defense comparison |
| finding | yes | CONFIRMED / REFUTED / CONTESTED |
| evidence | yes | Code locations with line numbers |
| step_execution | yes | Status for each step |

---

## Step Execution Checklist (MANDATORY)

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 0. External Flash Susceptibility Check | YES | Y/x/? | For each external protocol interaction |
| 1. Flash-Loan-Accessible State Inventory | YES | Y/x/? | |
| 2. Atomic Attack Sequence Modeling | YES | Y/x/? | For each accessible state |
| 3. Cross-Function Flash Loan Chains | YES | Y/x/? | |
| 3b. Flash-Loan-Enabled Debounce DoS | YES | Y/x/? | Shared cooldown functions |
| 3c. No-Op Resource Consumption | YES | Y/x/? | Zero-effect calls consuming resources |
| 3d. External Flash x Debounce Cross-Ref | YES | Y/x/? | Cross-reference 0 x 3b |
| 4. Flash Loan + Donation Compounds | IF BALANCE_DEPENDENT | Y/x(N/A)/? | |
| 5. Flash Loan Defense Audit | YES | Y/x/? | For each attack path |
| 5b. Defense Parity Audit | YES | Y/x/? | For each action in multiple modules |

## references/aptos/fork-ancestry.md

---
name: "fork-ancestry"
description: "Trigger Pattern Always (run during recon TASK 0, not breadth) - Inject Into Recon agent only (meta_buffer.md enrichment)"
---

# FORK_ANCESTRY Skill -- Aptos

> **Trigger Pattern**: Always (run during recon TASK 0, not breadth)
> **Inject Into**: Recon agent only (meta_buffer.md enrichment)
> **Purpose**: Detect known parent codebases and inherit their historical vulnerability patterns.

## 1. Detect Fork Indicators

Grep the codebase for known parent signatures:

| Parent Project | Detection Patterns | Common Forks |
|---------------|-------------------|--------------|
| Thala | `thala\|thalaswap\|move_staking\|thala_manager\|stability_pool\|mod_coin` | Stableswap/staking forks |
| Echelon | `echelon\|lending_pool\|borrow_pool\|echelon_market\|lending_config` | Lending protocol forks |
| Aries | `aries\|aries_market\|margin_trade\|aries_profile` | Margin trading forks |
| Aptos Framework Staking | `delegation_pool\|stake_pool\|validator_set\|staking_config` | Delegation/staking forks |
| Liquidswap | `liquidswap\|curves\|liquidity_pool\|coin_helper\|lp_coin` | DEX forks (Pontem) |
| Pancakeswap | `pancake\|masterchef\|smart_router\|pancakeswap\|cake_token` | Yield farming forks |
| Amnis Finance | `amnis\|amnis_staking\|amapt\|stapt\|amnis_router` | Liquid staking forks |
| Cellana Finance | `cellana\|ve_token\|gauge\|voter\|bribe` | ve(3,3) / gauge forks |
| Merkle Trade | `merkle\|trading\|pnl_manager\|fee_distributor\|merkle_trading` | Perp DEX forks |
| Aptos Names (ANS) | `aptos_names\|domains\|ans_v2\|name_service` | Name service forks |
| Tortuga | `tortuga\|staked_aptos\|tortuga_staking\|tAPT` | Liquid staking forks |
| Ditto | `ditto\|ditto_staking\|staked_coin\|ditto_vault` | Liquid staking/vault forks |
| Aptos Token V2 / Digital Assets | `token::TokenV2\|collection\|aptos_token\|digital_asset` | NFT/token standard forks |
| Aptos Fungible Asset Framework | `fungible_asset\|FungibleStore\|FungibleAsset\|primary_fungible_store` | FA standard consumers |
| Pendleswap (Aptos) | `pendle\|pendleswap\|sy_token\|pt_token\|yt_token\|market_factory` | Yield tokenization forks |

**Output**: List of detected parents with confidence level (HIGH: 3+ patterns, MEDIUM: 2 patterns, LOW: 1 pattern).

## 2. Query Known Parent Issues

For each detected parent (confidence MEDIUM or HIGH):

### 2a. Solodit Search (two queries, run in parallel)
```
// Query 1: Known high-quality issues
search_solodit_live(
  protocol="{parent_name}",
  impact=["HIGH", "CRITICAL"],
  language="Move",
  quality_score=3,
  sort_by="Quality",
  max_results=15
)
// Query 2: Rare/unusual patterns specific to fork divergences
search_solodit_live(
  keywords="{parent_name} fork modified divergence aptos move",
  impact=["HIGH", "MEDIUM"],
  language="Move",
  sort_by="Rarity",
  max_results=10
)
```

### 2b. Tavily Search
```
tavily_search(query="{parent_name} aptos move smart contract vulnerability exploit audit finding 2024 2025 2026")
```

### 2c. Known Issue Catalog

Compile results into:

| Parent | Known Issue | Severity | Root Cause | Solodit Ref | Applicable to Fork? |
|--------|-----------|----------|------------|-------------|---------------------|
| {parent} | {issue title} | {severity} | {brief root cause} | {link/ID} | YES / NO / CHECK |

**Applicability criteria**:
- YES: Fork retains the vulnerable code path unchanged
- NO: Fork modified the vulnerable code path (document what changed)
- CHECK: Cannot determine without deeper analysis (flag for breadth agent)

### 2d. Hardcoded Known-Issue Floor (Web Search Fallback)
If Solodit AND Tavily BOTH fail, use this minimum catalog -- check EACH applicable parent:

| Parent | Critical Known Issue | Root Cause | Search Keywords |
|--------|---------------------|------------|-----------------|
| Thala | Stability pool share manipulation on first deposit | Empty pool rounding in share calculation | `thala stability pool first deposit share` |
| Liquidswap | LP token inflation via small initial liquidity | MINIMUM_LIQUIDITY equivalent missing or insufficient | `liquidswap lp token inflation first liquidity` |
| DEX yield farm (Aptos) | Reward rate manipulation via zero-amount deposit | Checkpoint timing + zero-amount triggers reward update | `masterchef aptos deposit zero reward` |
| Amnis Finance | Exchange rate manipulation between stAPT and amAPT | Discrete update timing allows entry at stale rate | `amnis finance exchange rate staleness stAPT` |
| Aptos Framework Staking | Delegation pool unlock timing + commission rate change | Validator can change commission before pending unlock completes | `delegation pool commission unlock timing aptos` |
| Echelon | Oracle price staleness in liquidation path | Stale price allows unfair liquidation or avoids valid liquidation | `echelon lending oracle staleness liquidation` |
| Cellana Finance | Vote-escrowed token lock bypass via gauge interaction | ve token accounting inconsistency during gauge deposit/withdraw | `cellana ve token lock gauge bypass` |
| Tortuga | Liquid staking share price manipulation via rewards timing | Reward distribution timing creates extractable arbitrage window | `tortuga liquid staking share price reward timing` |
| Aptos Fungible Asset Framework | Ref capability leak via public friend function | MintRef/TransferRef/BurnRef exposed through insufficiently restricted public(friend) function | `fungible asset ref capability leak public friend` |
| Aptos Token V2 | Object ownership transfer bypassing royalty enforcement | Token transfer via object::transfer bypasses marketplace royalty hooks | `aptos token v2 royalty bypass transfer` |

## 3. Divergence Analysis

For each detected parent:

### 3a. Identify What Changed

Compare fork vs parent in security-critical paths:

| Component | Parent Behavior | Fork Behavior | Security Impact |
|-----------|----------------|---------------|-----------------|
| {component} | {original} | {modified or SAME} | {new risk or NONE} |

Focus on:
- Modified access control (changed signer requirements, added/removed friend declarations)
- Changed mathematical formulas (fee calculations, exchange rates, reward distribution)
- Added external dependencies (new oracles, new CPI targets, new coin types)
- Removed safety checks (assertions removed, type constraints relaxed)
- Changed Ref storage patterns (different access control on stored MintRef/BurnRef/TransferRef)
- Module upgrade policy changes (parent `immutable` -> fork `compatible`, or vice versa)
- Generics changes (parent uses concrete types -> fork uses generics, or vice versa)

### 3b. New Attack Surface from Divergence

For each modification:
- Does the change introduce a NEW vulnerability not in the parent?
- Does the change REMOVE a parent fix/mitigation?
- Does the change create an INCONSISTENCY with parent's invariants?
- Does the change alter the Ref lifecycle (e.g., storing a Ref the parent consumed immediately)?

## 4. Output to meta_buffer.md

Append to `{SCRATCHPAD}/meta_buffer.md`:

```markdown
## Fork Ancestry Analysis

### Detected Parents
| Parent | Confidence | Patterns Found |
|--------|-----------|---------------|

### Inherited Vulnerabilities to Verify
| # | Parent Issue | Severity | Location in Fork | Status |
|---|-------------|----------|------------------|--------|
| 1 | {issue} | {severity} | {fork location} | CHECK / VERIFIED_SAFE / VULNERABLE |

### Fork Divergences (Security-Critical)
| # | Component | Change | New Risk? |
|---|-----------|--------|-----------|

### Questions for Breadth Agents
1. {derived from inherited vulnerabilities}
2. {derived from divergence analysis}
```

---

## Step Execution Checklist (MANDATORY)

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Detect Fork Indicators | YES | Y/N/? | |
| 2. Query Known Parent Issues | IF parent detected | Y/N(no parent)/? | |
| 3. Divergence Analysis | IF parent detected | Y/N(no parent)/? | |
| 4. Output to meta_buffer.md | YES | Y/N/? | |

## references/aptos/fungible-asset-security.md

---
name: "fungible-asset-security"
description: "Trigger FA_STANDARD flag detected (protocol uses FungibleAsset standard) - Used by Breadth agents, depth-token-flow"
---

# Skill: FUNGIBLE_ASSET_SECURITY

> **Trigger**: FA_STANDARD flag detected (protocol uses FungibleAsset standard)
> **Used by**: Breadth agents, depth-token-flow
> **Covers**: FungibleAsset metadata validation, zero-value exploitation, store ownership, dispatchable hooks, Ref safety, Coin-to-FA migration

## Purpose

Audit FungibleAsset standard usage for Aptos-specific vulnerabilities. The FA standard introduces object-based token management with capabilities (MintRef, BurnRef, TransferRef, FreezeRef) and optional dispatchable hooks. Incorrect usage creates counterfeit token acceptance, forced transfers, reentrancy, and accounting mismatches.

## Methodology

### STEP 1: Metadata Validation Audit

For EVERY function that accepts a `FungibleAsset` parameter or reads from a `FungibleStore`:

| # | Function | Accepts FA/Reads Store | Validates Metadata? | Expected Metadata | Bypass Possible? |
|---|----------|----------------------|--------------------|--------------------|-----------------|
| 1 | {func} | FungibleAsset param | YES/NO | {expected_metadata_obj} | YES/NO |

**How metadata validation works**:
```move
// CORRECT: validates the asset is the expected type
let metadata = fungible_asset::metadata(&fa);
assert!(metadata == expected_metadata, ERROR_WRONG_ASSET);

// VULNERABLE: no validation - accepts ANY FungibleAsset
public fun deposit(fa: FungibleAsset) {
    // Attacker can pass a worthless FA created from their own metadata
    fungible_asset::deposit(store, fa);
}
```

**MANDATORY SEARCH**: Grep all `.move` files for:
1. `FungibleAsset` in function signatures (parameters)
2. For each hit: trace whether `fungible_asset::metadata(&fa)` is called and compared
3. Functions that ONLY use `fungible_asset::amount(&fa)` without metadata check -> FLAG

**Severity**: Accepting unvalidated FungibleAsset = accepting counterfeit tokens. If the function credits the user or modifies protocol state based on the FA amount -> HIGH/CRITICAL.

### STEP 2: Zero-Value Exploitation

Analyze zero-value FungibleAsset paths:

| # | Zero-Value Source | Code Path Triggered | State Modified? | Cleanup Correct? |
|---|------------------|-------------------|----------------|-----------------|
| 1 | `fungible_asset::zero(metadata)` | {trace what happens} | YES/NO | YES/NO |
| 2 | Withdrawal of 0 amount | {trace} | YES/NO | YES/NO |

**Check for each**:
1. Can `fungible_asset::zero(metadata)` be used to trigger code paths that modify state? (e.g., register a user, set a flag, emit an event)
2. Does `fungible_asset::destroy_zero(fa)` clean up properly, or does it leave dangling state?
3. Can zero-value deposits/withdrawals:
   - Register a new FungibleStore where one shouldn't exist?
   - Trigger reward distribution checkpoints?
   - Bypass minimum deposit requirements (checked after or before deposit)?
   - Create entries in tracking data structures (SmartTable, vector)?
4. Does `amount == 0` get explicitly checked and rejected at entry points?

**Pattern**: Zero-value operations often bypass `amount > 0` checks that were assumed but never written, allowing state modifications without economic cost.

### STEP 3: Store Creation and Ownership Analysis

Audit FungibleStore creation, ownership chains, and access control:

#### 3a. Store Creation Inventory

| Store Type | Created By | Creation Permissionless? | Owner | Can Attacker Create? |
|-----------|-----------|-------------------------|-------|---------------------|
| Primary store | `primary_fungible_store::ensure_primary_store_exists()` | YES - anyone can create for any address | Address owner | YES (for any address) |
| Custom store | `fungible_asset::create_store()` on ConstructorRef | Only during object construction | Object owner | Depends on who can construct |

**CRITICAL**: `primary_fungible_store::ensure_primary_store_exists(addr, metadata)` is permissionless. An attacker can create a primary store for ANY address for ANY metadata. If the protocol assumes a store's existence means the user has interacted with the protocol -> FINDING.

#### 3b. Transitive Ownership

| Object A | Owns Object B | B Has FungibleStore | A Can Withdraw from B? |
|----------|-------------|--------------------|-----------------------|
| {object} | {child_object} | YES/NO | YES - via object ownership chain |

**Check**: If Object A owns Object B which owns a FungibleStore, the owner of Object A can withdraw from B's store through the ownership chain. Trace all object ownership hierarchies for unintended fund access paths.

#### 3c. Store Address Confusion

| Function | Expects Store At | Actually Reads From | Match? |
|----------|-----------------|--------------------|---------|
| {func} | Protocol-controlled store | User-supplied address | VERIFY |

**Pattern**: Protocol calculates expected store address but user can supply a different store address. If the function doesn't verify the store belongs to the expected object/address -> FINDING.

### STEP 4: Dispatchable Hook Analysis

If the protocol uses dispatchable FungibleAsset (custom `withdraw`, `deposit`, or `derived_balance` hooks):

#### 4a. Hook Inventory

| Hook Type | Registered? | Implementation Module | Can Reenter? | Can Revert? | Can Manipulate? |
|-----------|-------------|---------------------|-------------|------------|-----------------|
| withdraw | YES/NO | {module::func} | ANALYZE | ANALYZE | ANALYZE |
| deposit | YES/NO | {module::func} | ANALYZE | ANALYZE | ANALYZE |
| derived_balance | YES/NO | {module::func} | ANALYZE | N/A | ANALYZE |

#### 4b. Reentrancy via Hooks

For each registered hook:
1. Does the hook call back into the registering module's public functions?
2. Does the hook call into any other module that reads/writes shared state?
3. Is `#[module_lock]` applied to the registering module? (prevents indirect reentrancy but NOT direct)
4. What state has been modified BEFORE the hook executes? Can the hook see inconsistent state?

**Reentrancy sequence**:
```
Module::transfer() {
    1. Read balance (CHECK)
    2. Deduct from source store → triggers withdraw hook (INTERACTION before EFFECT completion)
    3. Withdraw hook reenters Module::another_function()
    4. another_function() sees partially-updated state
    // ...
}
```

#### 4c. Deposit Hook Blocking

Can a `deposit` hook unconditionally revert to prevent deposits into a specific store?
- If YES: can this be used to DoS the protocol? (e.g., prevent liquidations, block reward distribution)
- Who controls the hook? (protocol, user, external party)

#### 4d. Derived Balance Manipulation

If `derived_balance` hook is registered:
1. Does the protocol call `fungible_asset::balance(store)` expecting the real balance?
2. `balance()` calls `derived_balance` hook if registered - the returned value may differ from actual stored amount
3. Can the hook return inflated values to trick the protocol? (e.g., appear to have more collateral)
4. Can the hook return deflated values? (e.g., trigger incorrect liquidation)

### STEP 5: Ref Safety Analysis

Audit the lifecycle and access control of FungibleAsset capability references:

#### 5a. Ref Inventory

| Ref Type | Stored Where | Who Has Access | Can Be Extracted? | Impact If Leaked |
|----------|-------------|---------------|------------------|-----------------|
| MintRef | {object/resource} | {module/address} | YES/NO | Infinite token minting |
| BurnRef | {object/resource} | {module/address} | YES/NO | Destroy any user's tokens |
| TransferRef | {object/resource} | {module/address} | YES/NO | Bypass freeze, forced transfers |
| FreezeRef | {object/resource} | {module/address} | YES/NO | Freeze any user's store |

**MANDATORY CHECK** for each Ref:
1. Is the Ref stored in a resource with `key` only? (safe - not extractable)
2. Is the Ref stored in a struct with `store` ability? (dangerous - can be moved out)
3. Is the Ref stored in an Object? Who owns the Object? Can ownership be transferred?
4. Are there public functions that return the Ref or pass it to external code?

#### 5b. TransferRef Bypass Analysis

TransferRef allows transfers that bypass freeze status:
1. Is there a TransferRef for the protocol's main token?
2. Can TransferRef be used to force-transfer tokens FROM users? (`fungible_asset::transfer_with_ref(ref, from_store, to_store, amount)`)
3. Who holds the TransferRef? Is this documented as a trust assumption?
4. Can TransferRef bypass any protocol-level transfer restrictions (not just freeze)?

#### 5c. Ref Destruction Audit

| Ref Type | Can Be Destroyed? | Destruction Function | Consequences of Destruction |
|----------|------------------|---------------------|---------------------------|
| MintRef | NO (no destroy function) | N/A | Permanent minting capability |
| BurnRef | YES (burn_ref::destroy) | {if exists} | Cannot burn tokens anymore |
| TransferRef | {check} | {if exists} | Cannot force-transfer anymore |

### STEP 6: Coin-to-FA Migration Accounting

If the protocol handles both `Coin<T>` and `FungibleAsset`:

| # | Check | Status | Impact |
|---|-------|--------|--------|
| 1 | Are Coin and FA treated equivalently in balance accounting? | YES/NO | {if NO: describe discrepancy} |
| 2 | Does `total_supply` track both representations? | YES/NO | {if NO: supply tracking broken} |
| 3 | Can user deposit as Coin, then withdraw as FA (or vice versa), exploiting accounting difference? | YES/NO | {describe path} |
| 4 | Are there functions that only accept Coin but credit FA internally (or vice versa)? | YES/NO | {conversion correct?} |
| 5 | If protocol converts Coin<T> to FA: does `coin::coin_to_fungible_asset()` preserve exact amount? | VERIFY | {check for fees or rounding} |

**Pattern**: When a protocol accepts both Coin<T> and FungibleAsset for the same underlying token, internal accounting that tracks only one representation can be exploited by depositing in one form and withdrawing in the other.

## Key Questions (Must Answer All)

1. **Metadata validation**: Does every FA-accepting function verify the asset type?
2. **Zero-value**: Are zero-amount operations explicitly guarded?
3. **Store creation**: Can permissionless store creation be exploited?
4. **Hooks**: If dispatchable, can hooks reenter, block, or manipulate balances?
5. **Refs**: Where are MintRef/BurnRef/TransferRef/FreezeRef stored, and who can access them?
6. **Coin-FA parity**: If both types supported, is accounting consistent?

## Common False Positives

1. **Framework-enforced metadata**: Some framework functions internally validate metadata - verify before flagging
2. **Primary store determinism**: Primary store addresses are deterministic (`primary_fungible_store_address(owner, metadata)`) - "unexpected address" may be intentional
3. **Intentional TransferRef usage**: Protocol may document that TransferRef is needed for authorized transfers (e.g., liquidation)
4. **Zero-value guards in framework**: Some framework functions (e.g., `deposit`) may already reject zero amounts internally - verify

## Output Schema

```markdown
## Finding [FA-N]: Title

**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: ✓1,2,3,4,5,6 | ✗N(reason) | ?N(uncertain)
**Rules Applied**: [R1:✓/✗, R4:✓/✗, R10:✓/✗, R11:✓/✗]
**Severity**: Critical/High/Medium/Low/Info
**Location**: module_name.move:LineN

**FA Component**: {metadata/store/hook/ref/accounting}
**Attack Vector**: {counterfeit deposit / reentrancy via hook / forced transfer via TransferRef / ...}

**Description**: What's wrong
**Impact**: What can happen (fund theft, accounting mismatch, DoS)
**Evidence**: Code snippets showing the vulnerability
**Recommendation**: How to fix

### Precondition Analysis (if PARTIAL/REFUTED)
**Missing Precondition**: [What blocks exploitation]
**Precondition Type**: STATE / ACCESS / TIMING / EXTERNAL / BALANCE

### Postcondition Analysis (if CONFIRMED/PARTIAL)
**Postconditions Created**: [What conditions this creates]
**Postcondition Types**: [List applicable types]
**Who Benefits**: [Who can use these]
```

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Metadata Validation Audit | YES | ✓/✗/? | Every FA-accepting function checked |
| 2. Zero-Value Exploitation | YES | ✓/✗/? | |
| 3. Store Creation and Ownership | YES | ✓/✗/? | Primary store permissionless creation checked |
| 3b. Transitive Ownership | YES | ✓/✗/? | Object ownership chains traced |
| 4. Dispatchable Hook Analysis | IF dispatchable FA used | ✓/✗(N/A)/? | |
| 4b. Reentrancy via Hooks | IF hooks registered | ✓/✗(N/A)/? | |
| 4c. Deposit Hook Blocking | IF deposit hook registered | ✓/✗(N/A)/? | |
| 4d. Derived Balance Manipulation | IF derived_balance hook | ✓/✗(N/A)/? | |
| 5. Ref Safety Analysis | YES | ✓/✗/? | All 4 Ref types located and access traced |
| 5b. TransferRef Bypass | IF TransferRef exists | ✓/✗(N/A)/? | |
| 6. Coin-to-FA Migration Accounting | IF both Coin and FA supported | ✓/✗(N/A)/? | |

If any step skipped, document valid reason (N/A, no dispatchable hooks, no Coin support, no TransferRef).

## references/aptos/migration-analysis.md

---
name: "migration-analysis"
description: "Trigger Protocol has migration patterns (reinitialize, V2/V3, deprecated, upgrade, legacy, Coin-to-FA) - Covers Token type mismatches, stranded assets, interface incompatibiliti..."
---

# Skill: MIGRATION_ANALYSIS

> **Trigger**: Protocol has migration patterns (reinitialize, V2/V3, deprecated, upgrade, legacy, Coin-to-FA)
> **Covers**: Token type mismatches, stranded assets, interface incompatibilities, module upgrade safety
> **Required**: YES when MIGRATION flag detected

## Aptos Migration Context

Aptos modules are upgradeable by default under the `compatible` upgrade policy. Key differences from EVM:
- Module upgrades are **in-place** (same address, same module name)
- Resources in global storage **persist** across upgrades unchanged
- New functions can be added; existing public function signatures **must remain compatible**
- Storage layout must be compatible: new fields only at end of structs, existing fields unchanged
- `immutable` policy freezes module permanently; `compatible` allows additive changes
- The `Coin<T>` to `FungibleAsset` migration is a major ecosystem-wide transition

## Trigger Patterns

```
V2|V3|_deprecated|migrat|upgrade|legacy|old_token|new_token|coin_to_fungible|fungible_asset_to_coin|reinitialize
```

## Reasoning Template

### Step 1: Identify Token Transitions

Find all token migration patterns:
- `Coin<T>` to `FungibleAsset` migration (ecosystem-wide)
- Legacy module interfaces still referenced
- Deprecated functions still callable
- V1 -> V2 module patterns within the protocol

For each transition:

| Old Standard/Type | New Standard/Type | Migration Function | Bidirectional? | Framework Support? |
|-------------------|-------------------|-------------------|----------------|-------------------|
| Coin<CoinType> | FungibleAsset | coin::coin_to_fungible_asset | YES (coin::fungible_asset_to_coin) | aptos_framework |
| ModuleV1::Resource | ModuleV2::Resource | custom migrate() | {YES/NO} | N/A |

### Step 2: Check Interface Compatibility

For each external call that involves migrated tokens or upgraded modules:
1. What type does the CALLER expect? (`Coin<T>` or `FungibleAsset` or `Object<Metadata>`)
2. What type does the CALLEE actually return/accept?
3. Are they the same?
4. Has the external module upgraded to use a different standard?

```move
// Example mismatch:
// Protocol still uses Coin<USDC>
public fun deposit(coin: Coin<USDC>) { ... }
// But external DEX now returns FungibleAsset
public fun swap(...): FungibleAsset { ... }
// Mismatch: protocol receives FA but expects Coin
```

**Aptos-specific**: Check if external modules have migrated from `coin` to `primary_fungible_store` while the protocol still uses the `coin` interface. The aptos_framework provides automatic pairing between `Coin<T>` and its corresponding FungibleAsset, but this pairing has edge cases.

### Step 3: Trace Token Flow Paths

For each function that interacts with migrated tokens:

1. **Entry point**: What token standard does the user provide?
2. **Internal flow**: What standard does the protocol track internally?
3. **External call**: What standard does the external module expect?
4. **Return value**: What standard is returned?

| Function | User Provides | Protocol Tracks | External Expects | Mismatch? |
|----------|---------------|-----------------|------------------|-----------|

### Step 3b: External Side Effect Token Compatibility

When migration changes token types or interaction patterns, check whether external call side effects produce tokens that the current logic handles correctly.

For each external call that returns tokens or triggers side effects:

| External Call | Pre-Migration Side Effect | Post-Migration Side Effect | Logic Handles Both? | Mismatch? |
|---------------|--------------------------|---------------------------|---------------------|-----------|
| {ext_call} | Returns Coin<T> | Returns FungibleAsset | YES/NO | {describe} |

**Pattern**: Migration changes the primary token standard (e.g., Coin -> FA), but external modules still return the old standard as rewards, receipts, or side effects. The new logic may not handle the old token type.

**Check**: For each external dependency, does the post-migration logic correctly handle ALL token types that external calls can produce -- including legacy types from pre-migration interactions still in flight?

### Step 3c: Pre-Upgrade Resource Inventory

Before analyzing stranded asset paths, inventory what resources CURRENTLY EXIST in global storage:

| Resource Type | Published At | How It Arrived | Post-Upgrade Logic Handles? | Exit Path Post-Upgrade? |
|---------------|-------------|---------------|----------------------------|------------------------|
| Coin<T> store | User addresses | User deposits via coin::register + deposit | YES/NO | {function or NONE} |
| FungibleStore | Object addresses | primary_fungible_store::deposit | YES/NO | {function or NONE} |
| Custom resource | @protocol | Module initialization | YES/NO | {function or NONE} |

**Pattern**: Upgrade changes which token standard the protocol uses, but global storage still holds resources from pre-upgrade operations. If the new logic only handles FungibleAsset, Coin<T> balances at user addresses are stranded.

**Check**: For every resource type the protocol can create pre-upgrade:
1. Does the post-upgrade logic reference this resource type?
2. Is there a migration or sweep function that covers it?
3. If NEITHER -> STRANDED ASSET FINDING (apply Rule 9 severity floor)

### Step 4: Stranded Asset Analysis (Exhaustive)

> **CRITICAL**: This step uses exhaustive methodology. Every sub-step is MANDATORY.

#### 4a. Asset Inventory by Era

List ALL assets the protocol handles, categorized by migration era:

| Asset | V1 Entry Path | V2 Entry Path | V1 Exit Path | V2 Exit Path |
|-------|---------------|---------------|--------------|--------------|
| Coin<T> balance | deposit_coin() | N/A (removed) | withdraw_coin() | withdraw() converts? |
| FungibleAsset balance | N/A | deposit_fa() | N/A | withdraw_fa() |

**Rule**: If V1 Entry exists but V2 Exit does not handle V1 state -> potential stranding

#### 4b. Cross-Era Path Matrix

For EACH asset and EACH possible state combination:

| Asset Era | State Condition | Available Exit Paths | Works? | Reason |
|-----------|-----------------|---------------------|--------|--------|
| V1 Coin deposit | V2 logic active | withdraw() | Y/N | {does V2 read CoinStore?} |
| V1 Coin deposit | V1 functions removed in upgrade | withdraw_coin() | Y/N | {removed in compatible upgrade?} |
| V1 resource | In-flight during upgrade | ??? | Y/N | {resource persists but handler changed} |

**Aptos-specific**: Under `compatible` upgrade policy, public functions cannot be removed -- only new functions can be added. However, the function logic CAN change. A V1 function that previously handled Coin<T> might be updated to expect FungibleAsset internally, breaking for users with V1 state.

**STRANDING RULE**: If ALL exit paths = N for any state -> **STRANDED ASSETS FINDING**

#### 4c. Recovery Function Inventory

Document ALL functions that could recover stranded assets:

| Function | Who Can Call | What Assets Can Recover | Limitations |
|----------|--------------|------------------------|-------------|
| admin_rescue() | Admin signer | All resources at @protocol | Requires admin action |
| migrate_user() | Any user | User own Coin -> FA | One-time conversion |
| sweep() | Admin | Unaccounted tokens | Cannot recover user resources at their addresses |

**Question**: Is there a recovery path for EVERY stranding scenario in 4b?

#### 4d. Worst-Case Scenarios (MANDATORY)

Model these specific scenarios with code traces:

**Scenario 1: V1 Deposit + V2 Logic**
```
State: User deposited via V1 function, CoinStore<T> has balance
Event: Protocol upgraded to V2 (now uses FungibleAsset internally)
Question: Can user withdraw via V2 withdraw()?
Trace: [document code path -- does V2 read CoinStore or only FungibleStore?]
Result: [SUCCESS/STRANDED + amount]
```

**Scenario 2: Resource Persistence After Upgrade**
```
State: Custom resource R published at user address by V1 logic
Event: V2 module changes struct R layout (adds new field at end)
Question: Can V2 functions read/use the old R?
Trace: [compatible upgrade -- struct deserialization with new fields defaulting]
Result: [SUCCESS/ABORT + which functions break]
```

**Scenario 3: Paired Coin/FA Migration**
```
State: Protocol has Coin<T> and paired FungibleAsset for same underlying
Event: External module migrates to FA only, stops accepting Coin<T>
Question: Can protocol still interact with external module?
Trace: [does protocol auto-convert via coin::coin_to_fungible_asset?]
Result: [SUCCESS/STRANDED + which paths break]
```

#### 4e. Step 4 Completion Checklist

- [ ] 4a: ALL assets inventoried with entry/exit paths per era
- [ ] 4b: Cross-era path matrix completed for all state combinations
- [ ] 4c: Recovery functions enumerated with limitations
- [ ] 4d: All three worst-case scenarios modeled with code traces
- [ ] For EVERY stranding possibility: recovery path exists OR finding created

**Step Execution Output**: `check4a,4b,4c,4d,4e` or `?4X(incomplete reason)`

### Step 4f: User-Blocks-Admin Scenarios

Check whether user actions can create state that prevents admin migration or management operations:

| Admin/Migration Function | Precondition Required | User Action That Blocks It | Timing Window | Severity |
|--------------------------|----------------------|---------------------------|---------------|----------|
| {admin_func} | {precondition} | {user_action creating conflicting state} | {window size} | {assess} |

**Pattern**: Admin migration or management functions require certain state conditions (e.g., "no pending operations", "all users migrated", "no active borrows"). Users performing normal operations (deposits, withdrawals, claims) may create state that blocks these admin functions.

**Aptos-specific**: Resource existence checks (`exists<R>(addr)`) used as preconditions -- can a user create or destroy a resource to block admin functions?

**Check for each admin/migration function**:
1. What state preconditions does this function require?
2. Can a user create conflicting state using normal operations?
3. Is the blocking permanent or temporary?
4. Can the user be griefed into blocking (e.g., unsolicited resource creation at user address)?

If blocking is possible AND permanent -> minimum MEDIUM severity
If blocking is temporary but repeatable -> assess with Rule 10 worst-state

### Step 5: External Call Verification

For each external call:

1. Identify the ACTUAL external module version deployed (if verifiable)
2. Verify token standard accepted/returned
3. Compare with what the protocol sends/expects

| External Module | Function | Protocol Sends | Module Expects | Match? |
|----------------|----------|----------------|-----------------|--------|
| aptos_framework::coin | withdraw<T> | signer + amount | signer must have CoinStore<T> | VERIFY |
| dex_module::swap | swap() | FungibleAsset | FungibleAsset with correct metadata | VERIFY |
| oracle::get_price | get_price() | - | Returns u64 or FixedPoint64? | VERIFY |

### Step 6: Downstream Integration Compatibility

When the protocol changes token standards, interfaces, or behavior during migration, check how downstream consumers are affected:

| Protocol Change | Downstream Consumer Type | Expected Interface/Token | Actual Post-Migration | Breaking Change? |
|-----------------|--------------------------|--------------------------|----------------------|-----------------|
| {what changed} | DeFi integrations (DEX, lending) | {expected Coin<T>?} | {actual FA?} | YES/NO |
| {what changed} | Indexers/APIs | {expected events} | {actual events} | YES/NO |
| {what changed} | Other protocol modules | {expected function sig} | {actual -- compatible?} | YES/NO |
| {what changed} | View functions | {expected return type} | {actual return type} | YES/NO |

**Pattern**: Protocol migrates from Coin<T> to FungibleAsset, but downstream integrators still call the old Coin<T> interface. Under compatible upgrade policy, old functions remain callable but may behave differently internally.

**Check**:
1. What external systems consume this protocol outputs (tokens, events, view functions)?
2. Does the migration change what those systems receive?
3. Are downstream systems notified or do they auto-detect the change?
4. If breaking -> FINDING with severity based on downstream impact scope

## Key Questions (Must Answer All)

1. **Token Standard**: For each interaction, what standard is ACTUALLY used? (Coin<T> vs FungibleAsset)
2. **Migration Completeness**: Can ALL V1 assets be accessed/withdrawn via V2 paths?
3. **Interface Drift**: Have external modules upgraded their interfaces independently?
4. **Stranded Path**: Is there any combination of (old_state + new_logic) that traps funds?

## Common False Positives

1. **Intentional deprecation**: Old functions deliberately made no-op with clear migration path
2. **Framework auto-pairing**: Coin<T> and FungibleAsset are automatically paired by aptos_framework -- verify pairing handles the specific case
3. **Admin-controlled migration**: Stranded assets recoverable via admin functions with no trust issue
4. **Compatible upgrade guarantee**: Under `compatible` policy, public function signatures cannot change -- but internal behavior CAN

## Instantiation Parameters

```
{CONTRACTS}           -- List of modules to analyze
{MIGRATION_TYPE}      -- Coin-to-FA / V1-to-V2 / Module upgrade
{OLD_STANDARD}        -- What the protocol used before
{NEW_STANDARD}        -- What the protocol uses now
{EXTERNAL_MODULES}    -- External modules that may have migrated independently
```

## Output Schema

For each finding:

```markdown
## Finding [MG-N]: Title

**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: check1,2,3,4,5 | X(reason) | ?(uncertain)
**Severity**: Critical/High/Medium/Low/Info
**Location**: module::function (source_file.move:LineN)

**Token Transition**:
- Old: {old_standard/type}
- New: {new_standard/type}
- Mismatch Point: {where types diverge}

**Description**: What is wrong
**Impact**: What can happen (stranded funds, wrong accounting, DoS)
**Evidence**: Code showing mismatch

### Precondition Analysis (if PARTIAL/REFUTED)
**Missing Precondition**: [What blocks exploitation]
**Precondition Type**: STATE / ACCESS / TIMING / EXTERNAL / BALANCE

### Postcondition Analysis (if CONFIRMED/PARTIAL)
**Postconditions Created**: [What conditions this creates]
**Postcondition Types**: [List applicable types]
```

## Step Execution Checklist

After completing analysis, verify:
- [ ] Step 1: All token transitions identified
- [ ] Step 2: Interface compatibility checked for each
- [ ] Step 3: Token flow traced through all paths
- [ ] Step 3b: External side effect token compatibility checked
- [ ] Step 3c: Pre-upgrade resource inventory completed
- [ ] Step 4: Stranded asset scenarios enumerated (4a-4e)
- [ ] Step 4f: User-blocks-admin scenarios checked
- [ ] Step 5: External modules verified against actual behavior
- [ ] Step 6: Downstream integration compatibility assessed

If any step skipped, document valid reason (N/A, single token, no external deps, no downstream consumers).

## references/aptos/oracle-analysis.md

---
name: "oracle-analysis"
description: "Trigger Pattern ORACLE flag (required) - Inject Into Breadth agents, depth-external, depth-edge-case"
---

# ORACLE_ANALYSIS Skill

> **Trigger Pattern**: ORACLE flag (required)
> **Inject Into**: Breadth agents, depth-external, depth-edge-case
> **Purpose**: Analyze all oracle integrations in Aptos Move protocols for staleness, decimal errors, zero/negative prices, confidence intervals, multi-oracle aggregation, and failure modes

For every oracle the protocol consumes:

**STEP PRIORITY**: Steps 6 (Failure Modes) and 5c (Deviation Reference) are where HIGH/CRITICAL severity findings most commonly hide. Do NOT rush these steps. If constrained, skip conditional sections (4a-4d, 5a) before skipping 5c or 6.

## 1. Oracle Inventory

Enumerate ALL oracle data sources the protocol reads:

| Oracle | Type | Module Path | Functions Called | Consumers (protocol functions) | Update Frequency | Freshness Guarantee |
|--------|------|-------------|-----------------|-------------------------------|-----------------|---------------------|
| {name} | Pyth / Switchboard / Custom / On-chain TWAP | {module::path} | {get_price / get_result / etc.} | {list all} | {expected} | {documented or UNKNOWN} |

**Aptos oracle landscape**:
- **Pyth Network**: `pyth::price_feed` module, returns `Price { price: I64, conf: u64, expo: I64, publish_time: u64 }`
- **Switchboard**: `switchboard::aggregator` module, returns aggregator results with `mantissa` and `scale`
- **Custom price feeds**: Protocol-specific oracles using `Table` or `SmartTable` for price storage
- **On-chain TWAP**: DEX-derived time-weighted prices (Thala, LiquidSwap, Pontem)

**For each oracle**: What decision does the protocol make based on this data? (pricing, liquidation threshold, reward rate, rebase trigger, collateral valuation, etc.)

## 2. Staleness Analysis

For each oracle identified in Step 1:

### 2a. Staleness Checks Present?

| Oracle | Timestamp Checked? | Max Staleness Enforced? | Staleness Threshold | Appropriate? |
|--------|-------------------|------------------------|--------------------:|-------------|
| {name} | YES/NO | YES/NO | {seconds or NONE} | {analysis} |

**Pyth-specific**: Is `price.publish_time` compared against `timestamp::now_seconds()`? What max age is enforced?
**Switchboard-specific**: Is the aggregator's `latest_confirmed_round.round_open_timestamp` validated?

**If NO staleness check**: What happens when the oracle returns stale data?
- [ ] Protocol uses stale price for liquidations -- unfair liquidations
- [ ] Protocol uses stale price for minting -- mispriced assets
- [ ] Protocol uses stale price for swaps -- arbitrage opportunity
- [ ] Protocol uses stale rate for rewards -- incorrect distribution

### 2b. Stale Data Impact Trace

For each consumer function, trace the impact of receiving data that is {freshness_guarantee x 2} old:

| Consumer Function | Data Used | If Stale By {X}: Impact | Severity |
|-------------------|-----------|------------------------|----------|
| {function} | {price/rate} | {specific impact} | {H/M/L} |

### 2c. Pyth-Specific Checks

| Check | Code Reference | Status |
|-------|---------------|--------|
| `get_price()` or `get_price_no_older_than()` used? | {location} | {which} |
| `price.publish_time` freshness validated? | {location} | YES/NO |
| `price.price` (I64) sign checked (> 0)? | {location} | YES/NO |
| `price.conf` confidence interval checked? | {location} | YES/NO |
| `price.expo` (negative exponent) handled correctly? | {location} | YES/NO |
| Price feed ID hardcoded or configurable? | {location} | {which} |

### 2d. Switchboard-Specific Checks

| Check | Code Reference | Status |
|-------|---------------|--------|
| Aggregator authority validated? | {location} | YES/NO |
| Result staleness checked? | {location} | YES/NO |
| Min/max response thresholds enforced? | {location} | YES/NO |
| Aggregator config (min oracle results, variance threshold) appropriate? | {location} | YES/NO |

## 3. Decimal Normalization Audit

For each oracle data flow:

| Oracle | Oracle Decimals/Exponent | Consumer Expects | Normalization Applied? | Correct? |
|--------|------------------------|-----------------|----------------------|----------|
| {name} | {expo or scale} | {expected by math} | YES/NO | {analysis} |

**Pyth decimal handling**: Pyth uses `expo` field (typically negative, e.g., `expo = -8` means 8 decimal places). The actual price = `price.price * 10^expo`. Common errors:
- Treating `expo` as positive when it is negative
- Not converting I64 exponent to unsigned for power calculation
- Mixing Pyth's expo-based decimals with token decimals (Aptos Coin typically uses 8 decimals, but FungibleAsset varies)

**Switchboard decimal handling**: Uses `mantissa` and `scale` (or `decimals`). Actual value = `mantissa * 10^(-scale)`.

**MANDATORY GREP**: Search all oracle consumer files for hardcoded decimal constants: `100000000`, `1e8`, `10_000_000`, `DECIMAL`, `PRECISION`. For each hit: (1) Is this a decimal normalization constant? (2) Does it match the ACTUAL oracle's decimal format? (3) If the oracle feed changes or is swapped, does this constant break?

**Decimal chain trace**: For each arithmetic operation using oracle data, trace the full decimal chain: `oracle_output_decimals` -> `normalization_step` -> `consumer_expected_decimals`. If any step uses a hardcoded constant rather than reading decimals dynamically -> FINDING.

**Common decimal mismatches on Aptos**:
- Pyth USD feeds: `expo = -8` (8 decimals), but protocol assumes 18
- Aptos native Coin<T>: typically 8 decimals
- FungibleAsset: varies per metadata configuration
- Cross-multiplication without normalization: `price * amount` where price and amount have different decimal bases

### 3d. Decimal Grep Sweep (MECHANICAL -- MANDATORY)

Grep ALL oracle consumer files for `10_u128|pow\(10|DECIMALS|PRECISION|100000000|normalize`. For each match, fill:

| File:Line | Pattern | Hardcoded Value | Oracle's Actual Decimals | Match? |
|-----------|---------|-----------------|-------------------------|--------|

If ANY row shows Match=NO or oracle decimals UNKNOWN with hardcoded constant -> FINDING (R16).
Skipping this step is a Step Execution violation (x3d).

<!-- LOAD_IF: TWAP -->
## 4. TWAP-Specific Analysis

If protocol uses any TWAP oracle (DEX-derived, custom accumulator, etc.):

### 4a. TWAP Window Analysis

| TWAP Oracle | Window Length | Pool Liquidity | Manipulation Cost (est.) | Sufficient? |
|-------------|-------------|----------------|-------------------------|-------------|
| {oracle} | {seconds} | {USD value} | {estimated} | YES/NO |

**Rule of thumb**: TWAP window < 30 min AND pool TVL < $10M -> potentially manipulable.

### 4b. TWAP Arithmetic

| Check | Status | Impact if Wrong |
|-------|--------|-----------------|
| Overflow protection on cumulative price difference? | YES/NO | {impact} |
| Geometric vs arithmetic mean -- correct for use case? | {which used} | {impact if wrong} |
| Time-weighted vs block-weighted -- which is used? | {which} | {manipulation vector} |
| Empty observation slots handled? | YES/NO | {impact} |
| Aptos epoch boundaries handled? (epoch changes can affect timestamps) | YES/NO | {impact} |

### 4c. TWAP Lagging Behavior

During rapid price movements, TWAP lags spot price. Trace:
- What happens when TWAP price is significantly lower than spot? (discounted minting/borrowing)
- What happens when TWAP price is significantly higher than spot? (premium liquidations)
- Is this lag exploitable by attackers who can predict the direction?

### 4d. TWAP Cold-Start Analysis

Check oracle behavior when history is insufficient: (1) zero snapshots, (2) single snapshot, (3) window period not yet elapsed.

| Cold-Start State | Oracle Return Value | Protocol Behavior | Exploitable? |
|------------------|--------------------:|-------------------|-------------|

For each exploitable state: can attacker act during cold-start window at manipulated price? Tag: [BOUNDARY:snapshots=0], [BOUNDARY:snapshots=1].
If TWAP returns 0 or aborts during cold-start with no fallback -> FINDING (R16, minimum Medium).
<!-- END_LOAD_IF: TWAP -->

## 5. Oracle Weight / Threshold Boundaries

For multi-oracle systems or oracle-based thresholds:

<!-- LOAD_IF: MULTI_ORACLE -->
### 5a. Multi-Oracle Systems

| Oracle System | Aggregation Method | Oracle Count | Agreement Required | What if Disagreement? |
|---------------|-------------------|-------------|-------------------|----------------------|
| {system} | Median / Mean / Weighted / First-valid | {N} | {M of N} | {fallback behavior} |

**Check**: What happens at exact threshold boundaries?
- If median of [100, 100, 101]: result = 100. Is that correct?
- If weighted average with equal weights rounds down: impact?
- If one oracle call aborts: does fallback handle it gracefully?
<!-- END_LOAD_IF: MULTI_ORACLE -->

### 5b. Oracle-Based Thresholds

| Threshold | Oracle Data Used | Threshold Value | At Exact Boundary | Off-by-One? |
|-----------|-----------------|----------------|-------------------|-------------|
| {name} | {oracle field} | {value} | {behavior at exact value} | YES/NO |

**Check `>` vs `>=`**: At the exact threshold value, does the protocol behave as intended?

### 5c. Deviation Reference Point Audit

For each deviation check in the protocol (maxDeviation, priceDeviation, deviationThreshold, etc.):

| Parameter | Measured Against | Reference Source | Reference Manipulable? | Reference Staleable? |
|-----------|-----------------|-----------------|----------------------|---------------------|

Checks:
1. What is the deviation MEASURED AGAINST? (previous on-chain price, TWAP, external oracle, hardcoded value)
2. Is the reference point itself manipulable? (e.g., if deviation checks current vs last-recorded, and last-recorded is admin-settable -> admin can set a stale reference that makes all future prices "within deviation")
3. Can the reference become stale? (e.g., if reference is updated only on specific actions, and those actions stop occurring)
4. Is the first recorded price special? (no prior reference -> deviation check may be bypassed on first update)
Tag: `[TRACE:deviation check: current vs {reference} -> reference source: {X} -> manipulable: {Y/N}]`

## 6. Oracle Failure Modes

For each oracle, model failure scenarios:

| Failure Mode | Oracle Behavior | Protocol Response | Impact | Mitigation Present? |
|-------------|-----------------|-------------------|--------|-------------------|
| Zero return | Returns price = 0 | {what happens} | {impact} | YES/NO |
| Abort | Call aborts (Move has no try/catch) | {what happens} | {impact} | YES/NO -- can_* check first? |
| Stale (freshness exceeded) | Returns old data | {what happens} | {impact} | YES/NO -- staleness check? |
| Extreme value | Returns outlier | {what happens} | {impact} | YES/NO -- bounds check? |
| Negative price (Pyth I64) | Returns < 0 | {what happens} | {impact} | YES/NO -- sign check? |
| Feed not initialized | Resource does not exist | {what happens} | {impact} | YES/NO -- exists<T> check? |

**Aptos-specific failure note**: Move does not have try/catch. Oracle call failures result in transaction abort. This means:
- External oracle call that aborts -> entire transaction reverts
- No graceful fallback unless protocol pre-checks oracle state with `exists<>` or similar
- Oracle DoS (feed stops updating) -> all dependent functions become uncallable

**For each unmitigated failure mode**: What is the worst-case impact? Can it lead to fund loss?

**Circuit breaker check**: Does the protocol have a mechanism to pause oracle-dependent operations if the oracle enters a failure state?

## Instantiation Parameters
```
{CONTRACTS}           -- Move modules to analyze
{ORACLE_MODULES}      -- Oracle module paths (pyth::price_feed, switchboard::aggregator, custom)
{CONSUMER_FUNCTIONS}  -- Functions that read oracle data
{PRICE_FEED_IDS}      -- Pyth price feed identifiers or Switchboard aggregator addresses
{TOKEN_DECIMALS}      -- Decimal configuration of tokens in scope
```

## Finding Template

```markdown
**ID**: [OR-N]
**Severity**: [based on fund impact and likelihood of oracle failure/manipulation]
**Step Execution**: checkmark1,2,3,4,5,6 | x(reasons) | ?(uncertain)
**Rules Applied**: [R1:Y, R4:Y, R10:Y, R16:Y]
**Location**: module::function:LineN
**Title**: Oracle [issue type] in [function] enables [attack/failure]
**Description**: [Specific oracle issue with data flow trace]
**Impact**: [Quantified impact under worst-case oracle scenario]
```

## Output Schema

| Field | Required | Description |
|-------|----------|-------------|
| oracle_inventory | yes | All oracle data sources and consumers |
| staleness_vectors | yes | Unmitigated staleness paths |
| decimal_mismatches | yes | Decimal normalization issues |
| failure_modes | yes | Oracle failure scenarios and protocol response |
| finding | yes | CONFIRMED / REFUTED / CONTESTED |
| evidence | yes | Code locations with line numbers |
| step_execution | yes | Status for each step |

---

## Step Execution Checklist (MANDATORY)

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Oracle Inventory | YES | Y/x/? | |
| 2. Staleness Analysis | YES | Y/x/? | For each oracle |
| 2c. Pyth-Specific Checks | IF Pyth used | Y/x(N/A)/? | |
| 2d. Switchboard-Specific Checks | IF Switchboard used | Y/x(N/A)/? | |
| 3. Decimal Normalization Audit | YES | Y/x/? | |
| 3d. Decimal Grep Sweep | YES | Y/x/? | MANDATORY mechanical step |
| 4. TWAP-Specific Analysis | IF TWAP used | Y/x(N/A)/? | |
| 4d. TWAP Cold-Start Analysis | IF TWAP used | Y/x(N/A)/? | Zero/single snapshot states |
| 5. Oracle Weight / Threshold Boundaries | IF multi-oracle or thresholds | Y/x(N/A)/? | |
| 5c. Deviation Reference Point Audit | IF deviation checks exist | Y/x(N/A)/? | Reference manipulability |
| 6. Oracle Failure Modes | YES | Y/x/? | For each oracle |

## references/aptos/reentrancy-analysis.md

---
name: "reentrancy-analysis"
description: "Trigger REENTRANCY flag detected (dynamic dispatch, closures, dispatchable FA, function values) - Used by Breadth agents, depth-state-trace"
---

# Skill: REENTRANCY_ANALYSIS

> **Trigger**: REENTRANCY flag detected (dynamic dispatch, closures, dispatchable FA, function values)
> **Used by**: Breadth agents, depth-state-trace
> **Covers**: Cross-module reentrancy via closures, dispatchable FA hook reentrancy, direct/indirect reentrancy, resource lock gaps

## Purpose

Audit reentrancy vectors in Aptos Move. Historically, Move's linear type system and static dispatch prevented reentrancy. Post Move 2.2, function values (closures) and dispatchable FungibleAsset hooks introduce dynamic dispatch, creating reentrancy surfaces analogous to EVM callbacks but with different mechanics and mitigations.

## Background: Aptos Reentrancy Model

**Pre Move 2.2**: No dynamic dispatch. All function calls are statically resolved at compile time. Reentrancy was architecturally impossible (no callbacks, no external calls to untrusted code).

**Post Move 2.2**: Two reentrancy vectors exist:
1. **Function values / closures**: `|arg| { body }` syntax allows passing executable code as parameters. A module calling a user-supplied closure can be reentered.
2. **Dispatchable FungibleAsset hooks**: `withdraw`, `deposit`, and `derived_balance` hooks execute external module code during FA operations. This is framework-level dynamic dispatch.

**`#[module_lock]`**: Prevents INDIRECT reentrancy (cross-module reentry into the locked module). Does NOT prevent DIRECT reentrancy (closure calling back into the same module's function within the same execution frame).

## Methodology

### STEP 1: Dynamic Dispatch Point Inventory

Find ALL uses of dynamic dispatch in the audited modules:

#### 1a. Function Values and Closures

**MANDATORY SEARCH**: Grep all `.move` files for:
1. `|` followed by parameter patterns (closure syntax: `|x| { ... }`, `|x, y| { ... }`)
2. Function types in signatures (e.g., `callback: |u64| -> u64`, `FunctionValue`)
3. `move |` (move closures that capture variables)
4. Functions that accept function-typed parameters

| # | Module | Function | Dynamic Dispatch Type | Caller-Controlled? | Reentrancy Risk |
|---|--------|----------|----------------------|-------------------|----------------|
| 1 | {module} | {func} | Closure parameter | YES/NO | {assess} |
| 2 | {module} | {func} | Stored function value | YES/NO | {assess} |

#### 1b. Dispatchable FA Hooks

**MANDATORY SEARCH**: Grep for:
1. `dispatchable_fungible_asset` module usage
2. `register_dispatch_functions` or equivalent hook registration
3. `withdraw_with_*`, `deposit_with_*` function patterns
4. `derived_balance` implementations

| # | Module | Hook Type | Registered Function | External Code Executed? |
|---|--------|-----------|--------------------|-----------------------|
| 1 | {module} | withdraw | {module::withdraw_hook} | YES - at every withdrawal |
| 2 | {module} | deposit | {module::deposit_hook} | YES - at every deposit |
| 3 | {module} | derived_balance | {module::balance_hook} | YES - at every balance query |

### STEP 2: Module Lock Analysis

For each module containing dynamic dispatch points:

| Module | Has `#[module_lock]`? | Public Entry Points | Protected by Lock? | Direct Reentry Possible? |
|--------|---------------------|--------------------|--------------------|------------------------|
| {module} | YES/NO | {list entry/public functions} | YES/NO | {YES if lock present - lock prevents indirect but not direct} |

**CRITICAL DISTINCTION**:
- `#[module_lock]` = YES: **Indirect** reentrancy blocked (Module A -> closure -> Module A's function). **Direct** reentrancy still possible (within same function frame, closure calls same module's public function via friend or inline).
- `#[module_lock]` = NO: Both direct and indirect reentrancy possible.

**Check**: For each module WITHOUT `#[module_lock]`:
1. Does it have any dynamic dispatch points (from Step 1)?
2. If YES: cross-module reentrancy is possible - trace all paths.

### STEP 3: Third-Party Resource Lock Bypass

If the audited module stores data in a third-party resource abstraction:

| Data Structure | Provided By Module | Our Module Uses | Third-Party Lock Protects Us? |
|---------------|-------------------|----------------|------------------------------|
| SmartTable | aptos_std | YES/NO | NO - their lock protects THEIR invariants, not ours |
| Table | aptos_std | YES/NO | NO |
| {custom_struct} | {third_party} | YES/NO | NO |

**Pattern**: Module A stores its accounting data in a SmartTable (from `aptos_std`). `aptos_std` may have `#[module_lock]`. But this lock only prevents reentry into `aptos_std` - it does NOT prevent reentry into Module A. An attacker can reenter Module A while Module A's SmartTable operation is in progress.

**Check**: Does the protocol rely on a third-party module's lock for its own reentrancy protection? If YES -> FINDING.

### STEP 4: State Consistency Analysis (Check-Effect-Interaction)

For each dynamic dispatch point identified in Step 1:

#### 4a. Pre-Dispatch State Snapshot

| Dispatch Point | State READ Before Dispatch | State MODIFIED Before Dispatch | State Modified AFTER Dispatch |
|---------------|--------------------------|------------------------------|------------------------------|
| {func:line} | {variables/resources read} | {variables/resources written} | {variables/resources written} |

#### 4b. Reentrancy Impact Trace

For each dispatch point where state is modified before dispatch:

```
1. Function entry: Read state S1 (e.g., user_balance = 100)
2. Modify state: S1 partially updated (e.g., user_balance -= 50, but total_supply not yet updated)
3. Dynamic dispatch: closure/hook executes
4. REENTRY: Attacker calls back into same module
5. Reentrant call reads: S1 (modified) - sees user_balance = 50
6. Reentrant call reads: S2 (NOT yet modified) - sees stale total_supply = 1000 (should be 950)
7. Inconsistency: S1 and S2 are out of sync
8. Original execution resumes: modifies S2 (total_supply = 950)
9. Impact: [describe what the attacker gained]
```

**Key question for each dispatch point**: Is there ANY pair of state variables (S1, S2) where S1 is updated before dispatch but S2 is updated after? If YES, the reentrant call sees an inconsistent state.

### STEP 5: Dispatchable FA Specific Reentrancy

If the protocol uses dispatchable FungibleAsset:

#### 5a. Withdraw Hook Reentrancy

```move
// Framework calls this DURING withdrawal:
fun withdraw_hook(store: Object<FungibleStore>, amount: u64, ...) {
    // This code runs AFTER the framework has decided to withdraw
    // but potentially BEFORE the calling module's post-withdrawal logic

    // Can this hook call back into the protocol?
    // What state has been partially modified at this point?
}
```

**Trace**: What is the call stack at the point the withdraw hook fires?
1. Protocol function (e.g., `redeem()`)
2. Framework `fungible_asset::withdraw()`
3. Hook: `module::withdraw_hook()`
4. Hook can call: ??? (any public function accessible)

#### 5b. Deposit Hook Blocking

Can a deposit hook selectively revert to block specific operations?
- If protocol performs a transfer (withdraw from A + deposit to B), can the deposit hook on B prevent the entire operation?
- Can this be used to grief liquidations, reward distributions, or time-sensitive operations?

#### 5c. Balance Query Reentrancy

If `derived_balance` hook is registered:
- Does calling `fungible_asset::balance()` trigger external code?
- Can this external code modify state that the caller depends on?
- Is `balance()` called within a state modification sequence? (read-modify-write pattern where read triggers hook)

### STEP 6: Mitigation Recommendations Framework

For each reentrancy vector found, categorize the recommended fix:

| Vector | Recommended Fix | Implementation |
|--------|----------------|----------------|
| Cross-module via closure | Add `#[module_lock]` | Module-level attribute |
| Direct reentrancy | Check-Effect-Interaction pattern | Reorder operations: all state writes before dispatch |
| Dispatchable FA hook | Complete all state updates before FA operations | Move all `borrow_global_mut` before `withdraw`/`deposit` |
| Third-party resource bypass | Module-level boolean guard | `assert!(!is_executing, E_REENTRANCY)` pattern |

## Key Questions (Must Answer All)

1. **Dynamic dispatch**: Does the module use function values, closures, or dispatchable FA hooks?
2. **Module lock**: Is `#[module_lock]` applied? What does it cover vs not cover?
3. **State ordering**: For each dispatch point, is all state fully updated before the dispatch?
4. **Third-party reliance**: Does the module rely on another module's lock for its own safety?
5. **Hook surface**: If dispatchable FA, which hooks are registered and who controls them?

## Common False Positives

1. **No dynamic dispatch**: If the module has zero closure parameters, zero function values, and does not use dispatchable FA, reentrancy is not possible in Move
2. **Read-only callbacks**: If the closure only reads state (no `borrow_global_mut`, no state writes), reentrancy cannot cause inconsistency
3. **Framework-only hooks**: If hooks are registered by the framework and not by user-controllable code, the hook code is trusted
4. **Module lock + no direct reentry**: If `#[module_lock]` is present AND the closure does not call the same module's functions, reentrancy is fully blocked
5. **Atomic transactions**: Move transactions are atomic - partial state is never visible cross-transaction (only within the same transaction via reentrancy)

## Output Schema

```markdown
## Finding [RE-N]: Title

**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: ✓1,2,3,4,5,6 | ✗N(reason) | ?N(uncertain)
**Rules Applied**: [R4:✓/✗, R10:✓/✗, R12:✓/✗]
**Severity**: Critical/High/Medium/Low/Info
**Location**: module_name.move:LineN

**Reentrancy Type**: DIRECT / INDIRECT / HOOK_BASED / THIRD_PARTY_BYPASS
**Dispatch Point**: {function:line where dynamic dispatch occurs}
**Inconsistent State**: {which state variables are out of sync during callback}

**Description**: What's wrong
**Impact**: What can happen (double-spend, state corruption, fund theft)
**Evidence**: Code showing the dispatch point and state ordering

### Attack Sequence
1. [Attacker calls function X]
2. [State S1 is modified]
3. [Dynamic dispatch triggers callback]
4. [Callback reenters function Y which reads stale S2]
5. [Impact: ...]

### Precondition Analysis (if PARTIAL/REFUTED)
**Missing Precondition**: [What blocks exploitation]
**Precondition Type**: STATE / ACCESS / TIMING / EXTERNAL / BALANCE

### Postcondition Analysis (if CONFIRMED/PARTIAL)
**Postconditions Created**: [What conditions this creates]
**Postcondition Types**: [List applicable types]
**Who Benefits**: [Who can use these]
```

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Dynamic Dispatch Point Inventory | YES | ✓/✗/? | Both closures (1a) and FA hooks (1b) |
| 2. Module Lock Analysis | YES | ✓/✗/? | Direct vs indirect distinction |
| 3. Third-Party Resource Lock Bypass | IF third-party data structures used | ✓/✗(N/A)/? | |
| 4. State Consistency Analysis | FOR EACH dispatch point | ✓/✗/? | Pre/post dispatch state traced |
| 5. Dispatchable FA Specific | IF dispatchable FA used | ✓/✗(N/A)/? | 5a, 5b, 5c sub-steps |
| 6. Mitigation Recommendations | FOR EACH finding | ✓/✗/? | |

If any step skipped, document valid reason (N/A, no dynamic dispatch, no dispatchable FA, module lock covers all paths).

## references/aptos/ref-lifecycle.md

---
name: "ref-lifecycle"
description: "Type Thought-template (instantiate before use) - Trigger Pattern Always (Aptos Move) -- ConstructorRef/TransferRef/MintRef/BurnRef lifecycle"
---

# Skill: Reference Lifecycle Analysis

> **Type**: Thought-template (instantiate before use)
> **Trigger Pattern**: Always (Aptos Move) -- ConstructorRef/TransferRef/MintRef/BurnRef lifecycle
> **Inject Into**: Breadth agents, depth-state-trace, depth-token-flow
> **Research basis**: Aptos Object model capability-based access control, permanent reference semantics

## Background

In Aptos Move, object capabilities (Refs) are unforgeable tokens that grant specific permissions over objects. Unlike role-based access control in EVM, Refs are permanent once created -- they CANNOT be revoked. A leaked or improperly stored Ref grants permanent capability to its holder.

Key Ref types:
- **ConstructorRef**: Created once during `object::create_*`. Parent of all other Refs. Grants ability to generate TransferRef, MintRef, BurnRef, DeleteRef, and ExtendRef.
- **TransferRef**: Grants ability to transfer an object even when its `TransferRef` is frozen. Bypasses `ungated_transfer` restrictions.
- **MintRef**: Grants ability to mint FungibleAsset. Unlimited minting if held.
- **BurnRef**: Grants ability to burn FungibleAsset from any FungibleStore.
- **DeleteRef**: Grants ability to delete an object.
- **ExtendRef**: Grants ability to generate a signer for the object, enabling further resource manipulation.

## Trigger Patterns
```
ConstructorRef|TransferRef|MintRef|BurnRef|DeleteRef|ExtendRef|
object::create_named_object|object::create_sticky_object|object::create_object|
fungible_asset::generate_mint_ref|fungible_asset::generate_burn_ref|
fungible_asset::generate_transfer_ref|object::generate_delete_ref|
object::generate_extend_ref|object::generate_transfer_ref
```

## Reasoning Template

### Step 1: Reference Inventory

Enumerate ALL Ref types found in the codebase. For each:

| Ref Type | Created In (module::function) | Stored Location | Access Control | Capability Granted |
|----------|-------------------------------|-----------------|----------------|--------------------|
| ConstructorRef | {module}::{init_fn} | {consumed / stored in resource} | {who can access} | Generate all other Refs |
| MintRef | {module}::{init_fn} | {global resource at @addr} | {who can access} | Unlimited minting of {asset} |
| BurnRef | {module}::{init_fn} | {global resource at @addr} | {who can access} | Burn {asset} from any store |
| TransferRef | {module}::{init_fn} | {global resource at @addr} | {who can access} | Transfer {asset} bypassing freeze |
| DeleteRef | {module}::{init_fn} | {global resource at @addr} | {who can access} | Delete {object} |
| ExtendRef | {module}::{init_fn} | {global resource at @addr} | {who can access} | Generate signer for {object} |

**Completeness check**: Search for ALL `generate_*_ref` calls and `object::create_*` calls. Every Ref created MUST appear in the table.

### Step 2: ConstructorRef Analysis

The ConstructorRef is the root capability. It exists only during the `init_module` or object creation call.

**Check 2a: Is ConstructorRef stored?**
- Search for any struct field of type `ConstructorRef` -- this type has `drop` but NOT `store`, so it CANNOT be stored in global storage directly.
- If code attempts to extract a signer from ConstructorRef via `object::generate_signer(&constructor_ref)` and stores the signer reference indirectly, trace what that signer can do.
- **Expected pattern**: ConstructorRef is consumed during init to generate other Refs, then dropped. It should NOT persist beyond the creation transaction.

**Check 2b: What Refs are generated from it?**
- List every `generate_*_ref` call that uses this ConstructorRef
- For each generated Ref: is it stored with appropriate access control?
- **FINDING trigger**: If ConstructorRef generates MintRef AND that MintRef is stored with `public` visibility or weak access control -> unlimited minting capability leak.

**Check 2c: ExtendRef derived signer**
- If `object::generate_extend_ref` is called, the resulting ExtendRef can later produce a signer via `object::generate_signer_for_extending`
- Trace ALL uses of this derived signer -- it can move resources, modify object state, and call `move_to`/`move_from`
- **FINDING trigger**: If ExtendRef is stored with weaker access control than the operations its signer can perform.

### Step 3: MintRef / BurnRef Analysis

**Check 3a: Storage access control**
- Where is MintRef stored? (must be in a resource at a controlled address)
- Who can call functions that borrow the MintRef? (check `acquires` and signer requirements)
- Is there any `public fun` that exposes MintRef via return value or mutable reference?
- **FINDING trigger**: `public fun` returning `&MintRef` or `&mut MintRef` = capability leak to any module.

**Check 3b: Mint amount validation**
- Does the minting function validate the amount? (cap, rate limit, per-epoch limit)
- Is there a supply cap enforced? (`fungible_asset::supply` check before mint)
- **FINDING trigger**: Unlimited minting with no cap = inflation vulnerability.

**Check 3c: BurnRef scope**
- `fungible_asset::burn_from` with a BurnRef can burn tokens from ANY FungibleStore
- Check: does the burn function require the store owner's authorization, or only the BurnRef?
- **FINDING trigger**: If BurnRef holder can burn from arbitrary user stores without authorization.

**Check 3d: Mint/Burn symmetry**
- If protocol has both MintRef and BurnRef: are they held by the same entity?
- Can one be used without the other? (mint without ability to burn = permanent inflation; burn without mint = permanent deflation)
- Are there economic invariants that depend on mint/burn balance?

### Step 4: TransferRef Analysis

**Check 4a: Freeze bypass**
- `fungible_asset::transfer_with_ref` bypasses frozen store checks
- If the protocol uses `fungible_asset::set_frozen_flag` for compliance/security: does a stored TransferRef undermine the freeze?
- **FINDING trigger**: TransferRef stored alongside freeze functionality = freeze can always be bypassed by TransferRef holder.

**Check 4b: Transfer direction**
- Can TransferRef be used to transfer FROM any store (withdrawal) or only TO (deposit)?
- `fungible_asset::transfer_with_ref` takes `from: Object<FungibleStore>` and `to: Object<FungibleStore>` -- the holder controls BOTH ends
- **FINDING trigger**: TransferRef holder can drain any FungibleStore of the associated asset.

**Check 4c: Who holds TransferRef?**
- If protocol stores TransferRef: who can invoke the transfer function?
- Is there a path where an external caller (not admin) can trigger a TransferRef-backed transfer?
- Trace all call paths from `public entry fun` to the `transfer_with_ref` invocation.

### Step 5: DeleteRef Analysis

**Check 5a: Resource cleanup before deletion**
- If `object::delete(delete_ref)` is called, what happens to resources stored at the object address?
- Move does NOT automatically clean up resources when an object is deleted -- resources become orphaned
- **FINDING trigger**: Object deletion without prior `move_from` of all resources = stranded assets (Rule 9: minimum MEDIUM).

**Check 5b: Deletion authorization**
- Who holds the DeleteRef? Can they delete an object that other users depend on?
- Is there a dependency check before deletion? (e.g., are there outstanding balances, active positions?)
- **FINDING trigger**: DeleteRef holder can delete a shared object (LP pool, vault) = griefing or fund loss.

### Step 6: Ref Leakage Path Analysis

**Check 6a: Public function returns**
- Search for ANY `public fun` or `public(friend) fun` that returns a Ref type
- Even `&MintRef` (immutable reference) leak is dangerous because it can be passed to `fungible_asset::mint` within the same transaction
- **Leakage severity**: `public fun` returning Ref = Critical leak (any module can use). `public(friend) fun` returning Ref = Medium leak (friend modules can use -- check friend list).

**Check 6b: Friend module exposure**
- List all `friend` declarations in modules that store Refs
- For each friend module: does it re-export the Ref or expose a `public fun` that uses the Ref without additional access control?
- **Transitive leak**: Module A stores MintRef, Module B is friend of A and gets MintRef access, Module B has `public fun` that calls Module A's mint function = any module can mint via Module B.

**Check 6c: Store ability check**
- Refs with `store` ability can be placed in arbitrary global storage locations
- Check: do any Ref types in the protocol have `store`? (standard Aptos Refs: ConstructorRef has `drop` only; MintRef/BurnRef/TransferRef/DeleteRef/ExtendRef have `drop` and `store`)
- **FINDING trigger**: Ref with `store` ability placed in a resource with weak access control = Ref can migrate to uncontrolled storage.

### Step 7: Ref Revocation Assessment

**Critical fact**: Aptos Refs CANNOT be revoked once created. There is no `revoke_mint_ref` function in the framework.

**Check 7a: Maximum blast radius**
- For each stored Ref: what is the maximum damage if the Ref holder is compromised?
- Document: if MintRef is compromised -> unlimited inflation. If TransferRef is compromised -> drain all stores. If ExtendRef is compromised -> arbitrary object state modification.

**Check 7b: Compensating controls**
- Since Refs cannot be revoked, does the protocol have compensating controls?
  - Pause mechanism that blocks functions using the Ref?
  - Multi-signer requirement before Ref-backed operations?
  - Rate limiting on Ref-backed operations?
- **FINDING trigger**: No compensating controls on a stored Ref with high blast radius = single point of failure.

**Check 7c: Module upgrade path**
- If the module is upgradeable (`compatible` or `immutable` policy?): can an upgrade change who accesses the Ref?
- If the module is immutable: the Ref access pattern is permanent -- any vulnerability is permanent.

### Step 8: Cross-Ref Interaction

**Check 8a: Ref combination attacks**
- Can MintRef + TransferRef be combined? (Mint tokens, then force-transfer them to a target store)
- Can BurnRef + TransferRef be combined? (Transfer tokens from victim store, then burn the evidence)
- Can ExtendRef + any other Ref be combined? (Generate signer to bypass access control, then use Ref)

**Check 8b: Ref holder alignment**
- Are all Refs held by the same entity? If different entities hold different Refs, model adversarial interaction.
- Example: Admin holds MintRef, Operator holds TransferRef. If Operator is compromised, they can drain stores. Admin cannot revoke TransferRef.

---

## Finding Template

```markdown
## Finding [{PREFIX}-N]: {Title}

**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: {see checklist below}
**Ref Type**: {ConstructorRef / MintRef / BurnRef / TransferRef / DeleteRef / ExtendRef}
**Severity**: {Critical/High/Medium/Low/Info}
**Location**: {SourceFile:LineN}
**Description**: {What capability is exposed and how}
**Impact**: {What an attacker/compromised entity can do with the Ref}
**Evidence**: {Code showing Ref creation, storage, and access path}

### Blast Radius
- **If compromised**: {Maximum damage description}
- **Revocable**: NO (Aptos Refs are permanent)
- **Compensating controls**: {pause/multisig/rate-limit or NONE}
```

---

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Reference Inventory | YES | Y/N/? | Must enumerate ALL Refs |
| 2. ConstructorRef Analysis | YES | Y/N/? | |
| 3. MintRef/BurnRef Analysis | IF present | Y/N(none)/? | |
| 4. TransferRef Analysis | IF present | Y/N(none)/? | |
| 5. DeleteRef Analysis | IF present | Y/N(none)/? | |
| 6. Ref Leakage Path Analysis | YES | Y/N/? | Check public returns + friends |
| 7. Ref Revocation Assessment | YES | Y/N/? | Always: Refs are permanent |
| 8. Cross-Ref Interaction | IF 2+ Ref types | Y/N(single)/? | |

### Output Format for Step Execution

```markdown
**Step Execution**: check1,2,3,4,6,7,8 | x5(no DeleteRef)
```

OR if incomplete:

```markdown
**Step Execution**: check1,2,3 | ?4,6,7(TransferRef not fully traced)
**FLAG**: Incomplete analysis -- requires depth review (leakage paths not exhausted)
```

## Instantiation Parameters
```
{CONTRACTS}           -- Move modules to analyze
{ASSET_NAME}          -- Primary fungible asset name
{REF_STORAGE}         -- Where Refs are stored (resource name and address)
{ACCESS_CONTROL}      -- Who can access stored Refs (signer requirements)
{FRIEND_MODULES}      -- Modules declared as friends
{FREEZE_USED}         -- Whether protocol uses freeze functionality (YES/NO)
{UPGRADE_POLICY}      -- Module upgrade policy (compatible/immutable)
```

## Output Schema
| Field | Required | Description |
|-------|----------|-------------|
| ref_inventory | yes | Complete table of all Refs in codebase |
| constructor_ref_analysis | yes | ConstructorRef lifecycle and consumption |
| mint_burn_analysis | if present | MintRef/BurnRef storage and access control |
| transfer_ref_analysis | if present | TransferRef and freeze bypass potential |
| delete_ref_analysis | if present | DeleteRef and resource cleanup |
| leakage_paths | yes | Public/friend exposure of Refs |
| revocation_assessment | yes | Blast radius and compensating controls |
| cross_ref_interactions | if 2+ types | Combined Ref attack scenarios |
| finding | yes | CONFIRMED / REFUTED / CONTESTED / NEEDS_DEPTH |
| evidence | yes | Code locations with line numbers |
| step_execution | yes | Status for each step |

## references/aptos/semi-trusted-roles.md

---
name: "semi-trusted-roles"
description: "Trigger Pattern SEMI_TRUSTED_ROLE flag (required) - Inject Into Breadth agents, depth-state-trace"
---

# SEMI_TRUSTED_ROLES Skill

> **Trigger Pattern**: SEMI_TRUSTED_ROLE flag (required)
> **Inject Into**: Breadth agents, depth-state-trace
> **Purpose**: Analyze semi-trusted roles in Aptos Move protocols using capability-based access control, modeling both role-to-user and user-to-role attack vectors

## Trigger Patterns
```
signer|SignerCapability|AdminCap|OperatorCap|KeeperCap|has_role|
assert_admin|assert_operator|friend|acquires|ExtendRef|
DeleteRef|TransferRef|MintRef|BurnRef
```

## Reasoning Template

### Step 1: Inventory Role Permissions

Enumerate ALL privileged roles in the protocol:

| Role | Capability / Check | Module | Functions Callable | State Modifiable | External Calls |
|------|-------------------|--------|-------------------|-----------------|----------------|
| {role} | {SignerCapability / custom Cap struct / signer check / friend} | {module} | {fn list} | {state list} | {calls list} |

**Aptos capability patterns to inventory**:
- **SignerCapability**: Stored in resource, allows generating a signer for the capability's address. Can call any function requiring that signer.
- **Custom capability structs**: `AdminCap`, `OperatorCap` etc. -- often stored in the deployer's account or an Object
- **Signer checks**: `assert!(signer::address_of(account) == @admin, E_NOT_ADMIN)` -- direct address comparison
- **Friend declarations**: `friend module::other` -- allows `other` to call `public(friend)` functions
- **Object Refs**: `ExtendRef`, `DeleteRef`, `TransferRef`, `MintRef`, `BurnRef` -- object-level capabilities
- **Resource account patterns**: Module creates a resource account and stores its `SignerCapability`

For each role at {ROLE_FUNCTIONS}:
- What state does it modify?
- What external calls does it make (via CPI or module calls)?
- What parameters does it accept?

### Step 2: Analyze Within-Scope Abuse (Direction A: Malicious Role)

For each permitted action, ask:

**Timing Abuse**:
- Can {ROLE_NAME} execute at harmful times? (front-run users via transaction ordering, during rebalance)
- Can {ROLE_NAME} delay execution to harm users? (withhold keeper actions)

**Parameter Abuse**:
- Can {ROLE_NAME} pass harmful parameters? (max slippage, wrong recipient address, extreme fee values)
- Are parameters validated on-chain, or trusted implicitly from the role?

**Sequence Abuse**:
- Can {ROLE_NAME} execute operations out of order?
- Can {ROLE_NAME} skip required operations in a multi-step process?

**Omission Abuse**:
- Can {ROLE_NAME} harm users by NOT acting? (skip price updates, delay distributions, never trigger harvest)

### Step 3: Model Attack Scenarios

```
Scenario A: Timing Attack
1. {ROLE_NAME} monitors mempool for user transaction {USER_ACTION}
2. {ROLE_NAME} front-runs with {ROLE_ACTION}
3. User's transaction executes with worse conditions
4. Impact: {TIMING_IMPACT}

Scenario B: Parameter Attack
1. {ROLE_NAME} calls {ROLE_FUNCTION} with {MALICIOUS_PARAMS}
2. Parameters are not validated against {EXPECTED_CONSTRAINTS}
3. Impact: {PARAM_IMPACT}

Scenario C: Key Compromise
1. {ROLE_NAME} private key is compromised (or SignerCapability is leaked)
2. Attacker can call: {ROLE_FUNCTIONS}
3. Maximum extractable value: {MAX_DAMAGE}
4. Recovery options: {RECOVERY_PATH}
```

### Step 4: Assess Mitigations

| Mitigation | Present? | Implementation | Effective? |
|-----------|----------|----------------|-----------|
| Timelock on role actions | YES/NO | {code ref} | {analysis} |
| Multisig requirement | YES/NO | {code ref} | {analysis} |
| Role revocation function | YES/NO | {code ref} | {analysis} |
| Rate limits / cooldowns | YES/NO | {code ref} | {analysis} |
| Parameter bounds validation | YES/NO | {code ref} | {analysis} |
| Event emission for monitoring | YES/NO | {code ref} | {analysis} |

**Does a removal/revocation function for {ROLE_NAME} EXIST?** If NO -> FINDING: role is irrevocable without module upgrade. Severity: minimum Medium if role can modify user-facing state.

### Step 4b: Capability Escalation Analysis

| Capability | Stored Where | Can Be Duplicated? | Can Escalate? | Escalation Path |
|-----------|-------------|-------------------|---------------|----------------|
| {cap} | {resource/object} | YES/NO (`copy` ability?) | YES/NO | {if YES: how} |

**Aptos-specific escalation vectors**:
- `SignerCapability` has `copy` + `store` abilities -- can it be extracted and stored elsewhere?
- `ExtendRef` allows adding resources to an Object -- can a role add capabilities it shouldn't have?
- `TransferRef` allows ungated transfer of an Object -- can a role transfer an Object holding other capabilities?
- `MintRef` / `BurnRef` -- can a role with mint capability effectively drain the protocol?
- Friend module access -- can a friend module be upgraded to abuse `public(friend)` functions?

### Step 4c: Capability Transfer and Duplication

| Capability | Has `copy`? | Has `drop`? | Has `store`? | Transfer Function Exists? | Risk |
|-----------|------------|------------|-------------|--------------------------|------|
| {cap} | YES/NO | YES/NO | YES/NO | YES/NO | {assessment} |

**Key checks**:
- If capability has `copy` -> it can be duplicated, creating multiple holders
- If capability has `store` -> it can be placed in global storage, potentially accessible by others
- If capability has `drop` -> it can be silently discarded (may not be a risk, but check if protocol assumes it persists)
- If a `transfer_cap()` function exists -> trace who can call it and whether it validates the recipient

## Reverse Perspective: User Exploitation of Roles

### Step 5: Model User-Side Exploitation (Direction B: Malicious Users)

**Predictability Analysis**:
- Is the role's behavior predictable? (scheduled tasks, triggered by events, MEV-visible)
- Can users observe when the role will act?
- Can users front-run or back-run the role's actions?

**Scenario D: User Exploits Keeper Timing**
```
1. User observes that {ROLE_NAME} executes {ROLE_ACTION} at predictable times
2. User positions themselves before {ROLE_ACTION} (front-running the keeper)
3. {ROLE_ACTION} executes, changing state
4. User benefits from known state change
5. Impact: {USER_EXPLOIT_IMPACT}
```

**Scenario E: User Griefs Role Preconditions**
```
1. {ROLE_FUNCTION} has precondition: {PRECONDITION}
2. User can manipulate state to violate {PRECONDITION}
3. {ROLE_NAME} calls {ROLE_FUNCTION}, which aborts
4. System enters degraded state (no keeper actions possible)
5. Impact: {GRIEF_IMPACT}
```

**Scenario F: User Forces Suboptimal Role Action**
```
1. {ROLE_NAME} must choose between options based on state
2. User manipulates state to make worst option appear best
3. {ROLE_NAME} (following honest behavior) chooses suboptimal path
4. User profits from forced suboptimal execution
5. Impact: {SUBOPTIMAL_IMPACT}
```

**Scenario G: Same-Chain Rate Staleness via Discrete Updates**
```
1. Protocol's exchange rate only updates when {ROLE_NAME} acts (discrete updates)
2. Between role actions, rate is stale -- does not reflect accumulated value
3. User monitors for {ROLE_NAME} pending transaction
4. User enters at stale rate (favorable), {ROLE_NAME} executes, rate updates
5. User exits at updated rate (or holds appreciating position)
6. Impact: {RATE_ARBIT_IMPACT}
```

### Step 6: Precondition Griefability Check

For each function callable by {ROLE_NAME}:

| Function | Preconditions | User Can Manipulate? | Grief Impact |
|----------|--------------|---------------------|--------------|
| {func} | balance > 0 | YES - withdraw all | Keeper stuck |
| {func} | cooldown passed | NO - time-based | N/A |
| {func} | threshold met | YES - partial withdraw | Delayed execution |
| {func} | resource exists | YES - can delete? | Function aborts |

**Generic Rule**: Any privileged function precondition that depends on user-manipulable state is potentially griefable.

### Step 6b: Admin/Privileged Function Griefability (EXHAUSTIVE)

**MANDATORY**: Enumerate ALL privileged functions by scanning for signer checks, capability acquires, and friend-only visibility. Do NOT rely on manual scanning.

For each function callable by admin or equivalent role:

| Function | Preconditions | External State Dependency? | User Can Manipulate? | Grief Impact |
|----------|--------------|---------------------------|---------------------|--------------|
| {admin_fn} | {preconditions} | YES/NO | YES/NO | {impact if griefed} |

**Enumeration completeness check**:
- [ ] Total role-restricted functions found: {N}
- [ ] Functions analyzed in this table: {M}
- [ ] If M < N -> INCOMPLETE -- analyze missing functions before proceeding

**Specific Aptos checks**:
- Can users create resources that block admin `move_from` operations?
- Can users deposit unsolicited tokens that prevent admin operations expecting zero balance?
- Can users initiate multi-step operations whose pending state blocks admin actions?
- Can users create Objects in a namespace that conflicts with admin Object creation?

## Key Questions (must answer ALL)

1. What is the maximum damage if {ROLE_NAME} acts maliciously?
2. What is the maximum damage if {ROLE_NAME} key/capability is compromised?
3. Are there time-sensitive operations where {ROLE_NAME} timing matters?
4. What user funds or protocol state can {ROLE_NAME} affect?
5. Can users predict when {ROLE_NAME} will act?
6. Can users manipulate preconditions to block {ROLE_NAME}?
7. Can users profit by positioning around {ROLE_NAME}'s scheduled actions?
8. What happens if {ROLE_NAME} cannot execute? (system degradation)
9. Can users block admin operations via state manipulation or unsolicited deposits?

## Common False Positives

- **View-only operations**: If role can only read state, no abuse vector
- **Idempotent operations**: If calling twice has same effect as once, timing abuse is limited
- **User-initiated dependency**: If role action requires user to initiate first, front-running may not apply
- **Economic alignment**: If role is economically aligned (staked collateral), malicious action has cost
- **Module upgrade authority**: Separate from in-protocol roles -- module upgrade is a governance concern, not a semi-trusted role issue (unless the protocol treats it as semi-trusted)

## Instantiation Parameters
```
{CONTRACTS}           -- Move modules to analyze
{ROLE_NAME}           -- Specific role (operator, keeper, admin, etc.)
{ROLE_FUNCTIONS}      -- Functions this role can call
{ROLE_CAPABILITIES}   -- Capability structs held by this role
{USER_ACTION}         -- User action that could be front-run
{ROLE_ACTION}         -- Role action used in attack
{TIMING_IMPACT}       -- Impact of timing attack
{MALICIOUS_PARAMS}    -- Harmful parameter values
{EXPECTED_CONSTRAINTS}-- What params should be validated against
{PARAM_IMPACT}        -- Impact of parameter attack
{MAX_DAMAGE}          -- Maximum extractable value
{RECOVERY_PATH}       -- How to recover from compromise
```

## Output Schema

| Field | Required | Description |
|-------|----------|-------------|
| role_permissions | yes | Functions and capabilities per role |
| timing_vectors | yes | Timing-based abuse opportunities |
| parameter_vectors | yes | Parameter-based abuse opportunities |
| omission_vectors | yes | Harm from inaction |
| capability_escalation | yes | Capability escalation and duplication risks |
| user_exploit_vectors | yes | How users can exploit the role (Direction B) |
| max_damage | yes | Worst-case damage assessment |
| mitigations | yes | Existing protections |
| finding | yes | CONFIRMED / REFUTED / CONTESTED / NEEDS_DEPTH |
| evidence | yes | Code locations with line numbers |
| step_execution | yes | Status for each step |

---

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Inventory Role Permissions | YES | | |
| 2. Analyze Within-Scope Abuse | YES | | |
| 3. Model Attack Scenarios (A,B,C) | YES | | |
| 4. Assess Mitigations | YES | | |
| 4b. Capability Escalation Analysis | YES | | Aptos-specific |
| 4c. Capability Transfer and Duplication | YES | | Aptos-specific |
| 5. Model User-Side Exploitation (D,E,F,G) | **YES** | | **MANDATORY** -- never skip |
| 6. Precondition Griefability Check | **YES** | | **MANDATORY** -- never skip |
| 6b. Admin Function Griefability | **YES** | | **MANDATORY** -- never skip |

### Cross-Reference Markers

**After Step 4** (Assess Mitigations):
- **DO NOT STOP HERE** -- Steps 5-6 analyze the reverse direction
- IF role has any preconditions depending on user state -> **MUST complete Step 6**

**After Step 4c** (Capability Transfer):
- IF capability has `copy` ability -> document duplication risk explicitly
- IF `SignerCapability` is stored -> trace ALL code paths that access it

**After Step 5** (User-Side Exploitation):
- Cross-reference with `TOKEN_FLOW_TRACING.md` for token-related griefing vectors
- IF keeper actions are predictable -> document MEV/front-running vectors

**After Step 6** (Precondition Griefability):
- IF any precondition is user-griefable -> severity >= MEDIUM
- Document system degradation if keeper is blocked

## references/aptos/share-allocation-fairness.md

---
name: "share-allocation-fairness"
description: "Trigger SHARE_ALLOCATION flag detected in pattern scan - Used by Breadth agents, depth-edge-case"
---

# Skill: SHARE_ALLOCATION_FAIRNESS

> **Trigger**: SHARE_ALLOCATION flag detected in pattern scan
> **Used by**: Breadth agents, depth-edge-case

## Purpose
Analyze fairness of share/token allocation mechanisms where users receive shares proportional to deposits, contributions, or participation -- checking for late-entry advantages, queue-position gaming, and time-weighting omissions. Adapted for Aptos Move FungibleAsset-based accounting and resource model.

## Methodology

### STEP 1: Classify Allocation Mechanism
Identify which pattern the protocol uses:

| Type | Pattern | Key Risk |
|------|---------|----------|
| Pro-rata snapshot | Shares minted at fixed ratio at deposit time | Late depositors dilute early depositors accrued value |
| Time-weighted | Shares accrue value based on duration held | Checkpoint manipulation, discrete vs continuous accrual |
| Queue-based | Deposits processed in batch/queue order | Queue position gaming, front-running batch processing |
| Epoch-based | Shares valued per epoch/period boundary | Cross-epoch timing arbitrage |

**Aptos-specific**: Identify whether shares are represented as:
- `FungibleAsset` with custom metadata (standard FA shares)
- `Coin<ShareType>` (legacy coin shares)
- Custom resource with balance field (non-standard)
- `Object<T>` with proportional ownership (object-based shares)

### STEP 2: Late Entry Attack Model
For each allocation entry point:

1. **Identify accrual source**: What generates value for existing share holders? (yield, fees, rewards, appreciation)
2. **Trace timing**: When does accrued value become claimable vs when can new shares enter?
3. **Check for time-weighting**: Does allocation account for HOW LONG shares were held, or only THAT shares are held?
4. **Model attack**: Can a depositor enter AFTER value accrues but BEFORE distribution, capturing value they did not earn?

| Entry Function | Accrual Source | Time-Weighted? | Late Entry Possible? | Impact |
|---------------|----------------|----------------|---------------------|--------|

**Aptos timing specifics**: Aptos block time is ~1 second. `timestamp::now_seconds()` granularity allows sub-epoch manipulation if epoch boundaries are timestamp-based. Check if the protocol uses `reconfiguration::last_reconfiguration_time()` or custom epoch tracking.

#### STEP 2c: Cross-Address Deposit Model
For each entry function accepting a beneficiary address or object parameter:

Check: what is the DEFAULT state for a never-before-seen beneficiary? Can depositing for a new address where that address has zero-initialized accounting unlock historical rewards, bypass cooldowns, or inherit accrued value?

| Entry Function | Accepts Beneficiary? | Default State for New Address | Exploitable? | Impact |
|---------------|---------------------|------------------------------|-------------|--------|

**Aptos-specific**: When a new `FungibleStore` is created for an address via `primary_fungible_store::ensure_primary_store_exists`, is the associated accounting state also initialized? Or does the share accounting resource exist independently from the token store?

If beneficiary != caller enables reward capture the recipient did not earn -> FINDING (late-entry variant).

#### STEP 2d: Pre-Setter Timing Model
For each admin-settable reward/rate parameter: model the sequence user_deposits -> admin_sets_rate -> rewards_accrue.
Does the user receive retroactive rewards for the period BEFORE the rate was set? Does a depositor after rate-setting receive the same, more, or less?

| Parameter Setter | Deposited-Before-Set? | Retroactive Rewards? | Fair? |
|-----------------|----------------------|---------------------|-------|

If depositing before rate-setting yields unearned rewards or causes reward loss for post-set depositors -> FINDING (timing fairness).

### STEP 2e: Pre-Configuration State Analysis

For the allocation mechanism identified in Step 1:

| Configuration Step | Parameter Set | Functions Available Before Set | Exploitable Default? |
|--------------------|-------------|-------------------------------|---------------------|

1. What is the module initialization sequence? List all `init_module` and manual configuration steps in order.
2. For each step: what functions are callable BEFORE this configuration completes?
3. Are there reward/share calculations that use unconfigured (zero/default) values?
4. Can a user deposit/stake before full configuration and receive outsized rewards/shares?
5. Is there a pause mechanism or `is_initialized` guard that prevents interaction before configuration completes?

**Aptos-specific**: `init_module` runs automatically on module publish. But additional configuration (setting rates, adding pools, registering tokens) often requires separate transactions. The window between `init_module` and full configuration is the attack surface.

If users can interact during partial configuration AND default values create unfair advantage -> FINDING (minimum Medium, Rule 13: design gap).

### STEP 3: Queue Position and Batch Processing
For protocols with batch/queue processing:

1. **Ordering fairness**: Is queue order FIFO, arbitrary (admin-chosen), or manipulable?
2. **Partial processing**: Can operator process some deposits but not others within a batch?
3. **Cross-batch state**: Does processing order within a batch affect allocation ratios?
4. **Deposit splitting**: Can a user split one large deposit into many small ones for queue advantage?

**Aptos-specific**: Aptos transaction ordering within a block is determined by the validator. If batch processing reads from a `Table` or `SmartTable`, iteration order may not be deterministic or FIFO. Check if the protocol uses `SmartVector` with explicit ordering or `Table` with unordered access.

### STEP 4: Share Redemption Symmetry
Check that entry and exit use consistent valuation:

1. **Mint vs burn ratio**: Are shares minted at the same exchange rate they can be burned?
2. **Pending claims**: Can unredeemed shares dilute active shares value?
3. **Withdrawal queue**: Does withdrawal ordering create unfair priority?

**Aptos-specific**: If shares are `FungibleAsset`, verify that `fungible_asset::supply()` is correctly tracked. If shares have `burn` capability, verify the burn-to-underlying ratio matches the mint ratio.

#### STEP 4b: Aggregate Constraint Coherence (Rule 14)
For independently-settable allocation rates/shares (e.g., per-pool weights, fee splits, distribution percentages):
Is the sum constraint enforced ON-CHAIN in the setter? Can each rate be changed independently without validating the aggregate?

| Rate/Weight Setter | Aggregate Constraint | Enforced On-Chain? | What if Sum Exceeds/Falls Short? |
|-------------------|---------------------|-------------------|--------------------------------|

If aggregate constraint NOT enforced and rates independently settable -> FINDING (Rule 14).

## Instantiation Parameters

```
{CONTRACTS}           -- List of modules to analyze
{SHARE_TOKEN}         -- Share/receipt token type (FA metadata, Coin type, custom resource)
{ENTRY_FUNCTIONS}     -- Functions that create/mint shares
{EXIT_FUNCTIONS}      -- Functions that burn/redeem shares
{RATE_SETTERS}        -- Admin functions that set allocation rates
{ACCRUAL_SOURCE}      -- What generates yield/value for share holders
```

## Output Schema

For each finding, specify:
- Allocation mechanism type
- Whether time-weighting is present or missing
- Concrete attack sequence with numerical example
- Who benefits and who is harmed

```markdown
## Finding [SA-N]: Title

**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: check1,2,2c,2d,2e,3,4,4b | X(reasons) | ?(uncertain)
**Rules Applied**: [R5:Y, R10:Y, R13:Y, R14:Y]
**Severity**: Critical/High/Medium/Low/Info
**Location**: module::function (source_file.move:LineN)

**Allocation Mechanism**: {type from Step 1}
**Fairness Violation**: {late-entry / queue-gaming / retroactive-reward / constraint-incoherence}

**Description**: What is wrong
**Impact**: Who is harmed and by how much (numerical example)
**Evidence**: Code showing allocation logic
```

---

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Classify Allocation Mechanism | YES | Y/X/? | |
| 2. Late Entry Attack Model | YES | Y/X/? | |
| 2c. Cross-Address Deposit Model | YES | Y/X/? | Check beneficiary != caller patterns |
| 2d. Pre-Setter Timing Model | YES | Y/X/? | Model deposit-before-rate-set sequence |
| 2e. Pre-Configuration State Analysis | YES | Y/X/? | init_module window + unconfigured defaults |
| 3. Queue Position and Batch Processing | IF queue/batch detected | Y/X(N/A)/? | |
| 4. Share Redemption Symmetry | YES | Y/X/? | |
| 4b. Aggregate Constraint Coherence | IF multiple settable weights | Y/X(N/A)/? | Rule 14 enforcement check |

If any step skipped, document valid reason (N/A, no queue, single pool, no settable weights).

## references/aptos/temporal-parameter-staleness.md

---
name: "temporal-parameter-staleness"
description: "Trigger Pattern TEMPORAL flag (required) - Inject Into Breadth agents, depth-state-trace"
---

# TEMPORAL_PARAMETER_STALENESS Skill

> **Trigger Pattern**: TEMPORAL flag (required)
> **Inject Into**: Breadth agents, depth-state-trace
> **Purpose**: Analyze cached parameters in multi-step operations that can become stale when admin/capability holders change them mid-operation, and external state stored and relied upon without re-verification

## Trigger Patterns
```
epoch|period|duration|delay|cooldown|lock_period|timelock|
unbonding_period|claim_delay|withdraw_delay|maturity_time|
pending_|request_|fulfill_|complete_|finalize_
```

## Reasoning Template

### Step 1: Enumerate Multi-Step Operations

Find all operations that span multiple transactions:

| Operation | Step 1 (Initiate) | Wait Condition | Step N (Complete) | Resource Storing State |
|-----------|-------------------|----------------|-------------------|-----------------------|
| {op_name} | {initiate_fn}() | {wait_condition} | {complete_fn}() | {PendingRequest / similar} |

**Aptos multi-step patterns**:
- Request/fulfill patterns: `request_withdraw()` -> wait for epoch/time -> `fulfill_withdraw()`
- Lock/unlock patterns: `lock()` -> cooldown expires -> `unlock()`
- Proposal/execute patterns: `propose()` -> voting period -> `execute()`
- Unstaking: `request_unstake()` -> unbonding period -> `claim()`
- Pending operations stored in `Table<address, PendingRequest>` or `SmartTable` or per-user resource

For each multi-step operation:
- What parameters are read/cached at Step 1 (stored in the pending resource)?
- What parameters are re-read at Step N?
- What parameters are used but NOT re-read at Step N?

### Step 2: Identify Cached Parameters

For each parameter used across steps:

| Parameter | Read At Step | Stored In | Admin-Changeable? | Re-Validated At Completion? |
|-----------|-------------|-----------|-------------------|----------------------------|
| {param} | initiate() L{N} | {PendingRequest.field} | YES/NO | YES/NO |
| {param} | initiate() L{N} | Not stored (read at completion from resource) | YES/NO | YES (re-read) |

**Red flags**: Parameter is cached in pending resource at Step 1 AND admin-changeable AND NOT re-validated at Step N.

**Aptos-specific caching patterns**:
- Parameters stored in global resource (`move_to` at initiation, `move_from` at completion)
- Parameters stored in `Table` entries keyed by user address
- Parameters stored in Object resources
- Parameters read from a separate config resource (may change between steps)

### Step 3: Model Staleness Impact

For each cached parameter that can become stale:

```
Scenario A: Parameter INCREASES between steps
1. User initiates at Step 1 with param = X (cached in PendingRequest)
2. Admin/capability holder changes param to X + delta in config resource
3. User completes at Step N
4. Impact: {what happens with stale value X when current is X + delta}

Scenario B: Parameter DECREASES between steps
1. User initiates at Step 1 with param = X (cached in PendingRequest)
2. Admin/capability holder changes param to X - delta in config resource
3. User completes at Step N
4. Impact: {what happens with stale value X when current is X - delta}
```

**BOTH directions are mandatory** -- increase and decrease often have different impacts.

**Common staleness impacts on Aptos**:
- Fee rate decreased after initiation -> user pays old (higher) fee at completion
- Withdrawal delay increased -> user can complete earlier than current policy allows
- Exchange rate changed -> user's pending operation uses outdated rate
- Collateral ratio changed -> user's pending position evaluated against stale threshold

### Step 3b: Update Source Audit (External State Staleness)

For each parameter updated from an external source:

| Parameter | External Source | Read When | Stored Where | Re-Read At Use? | Staleness Window |
|-----------|---------------|-----------|-------------|-----------------|-----------------|
| {param} | {oracle / other module / timestamp} | {read_fn} | {resource.field} | YES/NO | {time between read and use} |

**Analysis questions**:
- Is the source (e.g., oracle price, external module state, `timestamp::now_seconds()`) the correct representation of what this parameter tracks?
- Should this parameter be fixed for a period (e.g., per epoch, per cycle) rather than continuously refreshed?
- Which functions update it? Which functions SHOULD update it? Any mismatch?
- If external state is validated at entry point A, stored, then relied upon at entry point B without re-verification -> FINDING (R8 attack vector 4)
- **Unit consistency**: Verify all timestamp arithmetic uses consistent units. `timestamp::now_seconds()` returns seconds; `timestamp::now_microseconds()` returns microseconds. Mixing these without ×1_000_000 conversion in comparisons, subtractions, or staleness checks → FINDING.

### Step 4: Retroactive Application Analysis

For fee/rate parameters that apply to existing state:

| Parameter | Applies To | Retroactive? | Impact |
|-----------|-----------|--------------|--------|
| {fee_param} | {what it affects} | YES/NO | {if retroactive: who is harmed} |

**Pattern**: Fee changes that affect already-accrued rewards or already-initiated operations are retroactive.

**Aptos-specific retroactive risks**:
- Global fee rate stored in config resource, applied to ALL pending operations at completion
- Reward rate change affecting accumulated but unclaimed rewards
- Staking parameters changing for users already in unbonding period
- Exchange rate formula change applied to pending withdrawals

### Step 5: Assess Severity

For each staleness issue:

| Factor | Assessment |
|--------|-----------|
| Who is affected? | {single user / all users with pending ops / protocol} |
| Is the impact bounded? | {capped by fee range / max delay / parameter bounds} |
| Can it be exploited intentionally? | {admin front-running / user timing manipulation} |
| Is there a recovery path? | {re-initiate / admin override / cancel pending} |
| Worst-case fund impact? | {quantified amount or percentage} |

## Key Questions (must answer ALL)

1. What multi-step operations exist? (request/claim, deposit/lock/withdraw, propose/vote/execute)
2. For each cached parameter: can admin change it between steps?
3. What happens if a delay DECREASES after initiation? (users locked longer than necessary with old delay)
4. What happens if a delay INCREASES after initiation? (users can claim too early with old delay)
5. Are fees applied retroactively to existing positions or only to new ones?
6. Is there a maximum parameter range that bounds the staleness impact?

## Common False Positives

- **Immutable parameters**: If the parameter is set once at initialization and never changed, no staleness
- **Bounded ranges**: If min/max bounds limit the change magnitude, impact may be Low
- **User can re-initiate**: If users can cancel and restart with new parameters, reduced severity
- **Timelock protection**: If parameter changes require timelock, users have time to react
- **Epoch-bound parameters**: If parameters only change at epoch boundaries and operations complete within an epoch, no mid-operation staleness

## Instantiation Parameters
```
{CONTRACTS}           -- Move modules to analyze
{MULTI_STEP_OPS}      -- Identified multi-step operations
{CACHED_PARAMS}       -- Parameters cached at initiation (stored in pending resources)
{ADMIN_PARAMS}        -- Admin-changeable parameters (in config resources)
{DELAY_PARAMS}        -- Delay/cooldown parameters
{FEE_PARAMS}          -- Fee/rate parameters that may apply retroactively
```

## Output Schema

| Field | Required | Description |
|-------|----------|-------------|
| multi_step_ops | yes | List of multi-step operations found |
| cached_params | yes | Parameters cached across steps |
| staleness_vectors | yes | How cached params can become stale |
| external_staleness | yes | External state stored and relied upon without re-verification |
| retroactive_fees | yes | Fees applied retroactively |
| finding | yes | CONFIRMED / REFUTED / CONTESTED |
| evidence | yes | Code locations with line numbers |
| step_execution | yes | Status for each step |

---

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Enumerate Multi-Step Operations | YES | | |
| 2. Identify Cached Parameters | YES | | |
| 3. Model Staleness Impact (both directions) | YES | | |
| 3b. Update Source Audit (external state) | YES | | |
| 4. Retroactive Application Analysis | YES | | |
| 5. Assess Severity | YES | | |

### Cross-Reference Markers

**After Step 2**: If cached parameters are admin-changeable -> MUST complete Step 3 with BOTH increase and decrease scenarios.

**After Step 3b**: If external state is stored and re-used without re-verification -> cross-reference with ORACLE_ANALYSIS.md for oracle-sourced state.

**After Step 4**: Cross-reference with SEMI_TRUSTED_ROLES.md for admin functions that change these parameters and whether users can grief the parameter update mechanism.

## references/aptos/token-flow-tracing.md

---
name: "token-flow-tracing"
description: "Trigger Pattern BALANCE_DEPENDENT flag (required) - Inject Into Depth-token-flow, breadth agents"
---

# TOKEN_FLOW_TRACING Skill

> **Trigger Pattern**: BALANCE_DEPENDENT flag (required)
> **Inject Into**: Depth-token-flow, breadth agents
> **Purpose**: Trace all token flows through Aptos Move protocols using FungibleAsset and Coin<T> models, identifying accounting desync, unsolicited deposit vectors, type confusion, and dispatchable hook side effects

For every token the protocol handles:

## 1. Asset Inventory

Enumerate ALL asset types the protocol handles:

| Asset | Model | Type Parameter / Metadata | Decimals | Entry Modules | Exit Modules |
|-------|-------|--------------------------|----------|---------------|-------------|
| {name} | Coin<T> / FungibleAsset | {CoinType or metadata Object} | {decimals} | {list} | {list} |

**Aptos dual token model**:
- **Legacy Coin<T>**: Uses `CoinStore<T>` resource at user address. Type parameter `T` identifies the coin.
- **FungibleAsset (FA)**: Uses `FungibleStore` objects. `Metadata` object identifies the asset type.
- **Paired assets**: Some tokens exist as both Coin<T> and FA simultaneously (APT is the primary example). Check if protocol handles both representations correctly.
- **Migration tokens**: Tokens migrated from Coin to FA model may have both interfaces active.

## 2. Token Entry Points

Where can tokens enter the protocol?

| Entry Path | Function | Asset Model | Accounting Updated? | Access Control |
|------------|----------|-------------|--------------------|--------------|
| Standard deposit | {deposit_fn} | {Coin/FA} | YES/NO | {who can call} |
| `primary_fungible_store::deposit()` | External | FA | NO (protocol unaware) | Permissionless |
| `coin::deposit<T>()` | External | Coin<T> | NO (protocol unaware) | Permissionless (if CoinStore registered) |
| Direct `fungible_asset::deposit()` | Via store ref | FA | NO (protocol unaware) | Requires FungibleStore reference |
| `move_to<T>()` | Internal | Resource | {depends} | Module only |
| Side-effect receipts | External call returns | {varies} | {depends} | {depends} |

**Red flags**:
- Protocol holds a FungibleStore whose reference is obtainable by external callers
- Protocol has registered CoinStore<T> making it a valid deposit target
- Protocol uses `object::generate_signer()` or `object::generate_extend_ref()` which could allow external deposits

## 3. Token Exit Points

Where can tokens leave the protocol?

| Exit Path | Function | Asset Model | Accounting Updated? | Access Control |
|-----------|----------|-------------|--------------------|--------------|
| Standard withdraw | {withdraw_fn} | {Coin/FA} | YES/NO | {who can call} |
| `primary_fungible_store::withdraw()` | Via signer | FA | {depends} | Requires signer capability |
| `coin::withdraw<T>()` | Via signer | Coin<T> | {depends} | Requires signer |
| `fungible_asset::withdraw()` | Via store ref | FA | {depends} | Requires store `&mut` ref or TransferRef |
| Fee distribution | {fee_fn} | {varies} | YES/NO | {access} |
| Emergency withdraw | {emergency_fn} | {varies} | YES/NO | {admin} |

For each exit: does the tracked balance decrease BEFORE or AFTER the actual transfer?
For each transfer call: can the source account be underfunded at execution time? (funds deployed externally, locked, or lent out → transfer reverts)

### 3b. Self-Transfer Accounting
For each transfer function: can the sender and recipient be the same account/address?
If YES: does a self-transfer update accounting state (fees credited, rewards claimed, snapshots updated, share ratios changed) without net token movement? Flag as FINDING.

## 4. Balance Tracking Analysis

For each asset type:

| Asset | Internal Tracking Variable | On-Chain Balance Query | Can Desync? | Desync Vector |
|-------|---------------------------|----------------------|-------------|---------------|
| {name} | {e.g., total_deposited in resource} | `fungible_asset::balance(store)` or `coin::balance<T>(addr)` | YES/NO | {if YES: how} |

**Critical question**: Does the protocol use internal accounting or direct on-chain balance queries?

- **Internal accounting**: Protocol maintains its own `total_deposited` / `total_assets` resource -> SAFE from donation attacks IF consistently updated
- **Direct balance query**: Protocol reads `fungible_asset::balance()` or `coin::balance<T>()` directly -> **DONATION ATTACK VECTOR** -- attacker can inflate balance without protocol awareness

**Red flags**:
- Exchange rate calculations using `fungible_asset::balance(store)` directly
- No reconciliation function to handle accounting discrepancies
- Accounting variables updated BEFORE token transfer completes (not relevant in Move's linear type system, but check for resource mutation ordering)

## 5. Unsolicited Deposit Analysis

Can tokens be deposited to the protocol without calling its deposit function?

If **YES** (most cases on Aptos):

### 5a. Unsolicited Deposit Vectors

| Vector | Asset Model | Protocol Aware? | Impact |
|--------|-------------|----------------|--------|
| `primary_fungible_store::deposit(protocol_addr, fa)` | FA | NO | {impact} |
| `coin::deposit<T>(protocol_addr, coin)` | Coin<T> | NO | {impact} |
| Direct transfer to object-owned store | FA | NO | {impact} |

### 5b. Unsolicited Transfer Matrix (All Token Types) -- R11 Five Dimensions

For EVERY external token type the protocol holds, queries, or receives as side effects:

| Token Type | Can Deposit Unsolicited? | Accounting Distortion? | Share Inflation? | Threshold Manipulation? | Reward Dilution? | Fee Calculation Impact? |
|------------|------------------------|----------------------|-----------------|----------------------|-----------------|----------------------|
| {token_a} | YES/NO | YES/NO | YES/NO | YES/NO | YES/NO | YES/NO |

**RULE**: If ANY token type is unsolicited-depositable AND affects state -> analyze each consequence:
- **Accounting distortion**: Does tracked vs actual balance diverge?
- **Share inflation**: Does unsolicited deposit inflate share price (more assets per share)?
- **Threshold manipulation**: Can unsolicited deposits push protocol past thresholds?
- **Reward dilution**: Do unsolicited deposits dilute rewards for existing participants?
- **Fee calculation**: Do fees computed on balance include unsolicited deposits?

If **NO**:
- Why not? (No CoinStore registered? Store not publicly accessible? Custom deposit hooks reject?)
- Is the protection reliable? (Can it be bypassed via object manipulation?)

## 6. Token Type Confusion

Can the wrong asset type be used where another is expected?

| Check | Location | Status | Impact |
|-------|----------|--------|--------|
| FungibleAsset metadata validated on deposit? | {fn} | YES/NO | Wrong FA type accepted |
| Coin<T> type parameter constrains to expected type? | {fn} | YES/NO (compile-time) | N/A for Coin (type-safe) |
| Paired Coin/FA confusion? | {fn} | YES/NO | Same asset counted twice |
| Metadata address hardcoded or validated? | {fn} | {which} | Spoofed metadata |

**Aptos-specific type confusion vectors**:
- FungibleAsset metadata is an Object address -- if not validated, attacker-created FA with fake metadata could be deposited
- Coin<T> is type-safe at compile time (T must match), but FungibleAsset is identified by metadata Object at runtime
- If protocol accepts both Coin<T> AND FungibleAsset for the same underlying token, can the same deposit be counted under both models?

## 7. Dispatchable Hook Impact

If any token in scope uses the Aptos FungibleAsset dispatchable hooks (`DispatchFunctionStore`):

| Token | Hook Type | Hook Function | Side Effect | Protocol Handles? |
|-------|-----------|--------------|-------------|-------------------|
| {token} | deposit | {module::fn} | {effect} | YES/NO |
| {token} | withdraw | {module::fn} | {effect} | YES/NO |
| {token} | derived_balance | {module::fn} | {effect} | YES/NO |

**Analysis questions**:
- Can deposit hooks cause reentrancy-like behavior? (hook calls back into protocol during deposit)
- Can withdraw hooks block withdrawals? (hook aborts on certain conditions)
- Can derived_balance hooks return manipulated values? (custom balance reporting)
- Does the protocol check `is_dispatchable()` before interacting with tokens?

## 8. Zero-Value Operations

What happens with zero-amount deposits/withdrawals?

| Operation | Zero Amount Behavior | Accounting Impact | Shares Issued/Burned? |
|-----------|---------------------|-------------------|----------------------|
| deposit(0) | {aborts/succeeds} | {state change?} | {0 shares / abort?} |
| withdraw(0) | {aborts/succeeds} | {state change?} | {0 shares burned?} |
| transfer(0) | {aborts/succeeds} | {state change?} | N/A |

**Red flags**:
- Zero-amount deposit succeeds and issues shares (division by zero in rate calculation)
- Zero-amount withdraw triggers reward claims or state updates without actual token movement
- Zero-amount operations bypass minimum balance checks

## 9. Cross-Token Interactions

For protocols handling multiple token types:

| Token A | Token B | Interaction | Can A Affect B? | Impact |
|---------|---------|-------------|----------------|--------|
| {tokenA} | {tokenB} | {rate dependency / collateral relationship / swap} | YES/NO | {impact} |

- Can operations on TokenA affect TokenB's accounting?
- Are there exchange rate dependencies between tokens?
- Can withdrawing TokenA affect availability of TokenB?
- If protocol handles both Coin<T> and FA representations of same asset, are operations on one reflected in the other?

## 10. Token Flow Checklist

For each token identified:

| Token | Entry Points | Exit Points | Tracking Var | Direct Balance Query Used? | Unsolicited Possible? |
|-------|--------------|-------------|--------------|---------------------------|----------------------|
| {name} | {list} | {list} | {var} | YES/NO | YES/NO |

## Instantiation Parameters
```
{CONTRACTS}           -- Move modules to analyze
{ASSET_TYPES}         -- FungibleAsset metadata and Coin<T> types in scope
{ENTRY_FUNCTIONS}     -- Functions where tokens enter the protocol
{EXIT_FUNCTIONS}      -- Functions where tokens leave the protocol
{BALANCE_VARS}        -- Internal balance tracking variables
{EXTERNAL_TOKENS}     -- External token types the protocol interacts with
```

## Finding Template

```markdown
**ID**: [TF-N]
**Severity**: [based on fund impact]
**Step Execution**: checkmark1,2,3,4,5,6,7,8,9,10 | x(reasons) | ?(uncertain)
**Rules Applied**: [R1:Y, R4:Y, R10:Y, R11:Y]
**Location**: module::function:LineN
**Title**: [Asset type] can enter/exit via [path] without [expected accounting update]
**Description**: [Trace the token flow and where it diverges from expected]
**Impact**: [What breaks: exchange rates, user balances, protocol insolvency]
```

## Output Schema

| Field | Required | Description |
|-------|----------|-------------|
| asset_inventory | yes | All asset types and their models |
| entry_points | yes | Token entry paths with accounting status |
| exit_points | yes | Token exit paths with accounting status |
| balance_tracking | yes | Internal vs on-chain balance analysis |
| unsolicited_vectors | yes | Unsolicited deposit analysis (R11 5 dimensions) |
| type_confusion | yes | Token type validation issues |
| finding | yes | CONFIRMED / REFUTED / CONTESTED |
| evidence | yes | Code locations with line numbers |
| step_execution | yes | Status for each step |

---

## Step Execution Checklist (MANDATORY)

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Asset Inventory | YES | Y/x/? | |
| 2. Token Entry Points | YES | Y/x/? | |
| 3. Token Exit Points | YES | Y/x/? | |
| 4. Balance Tracking Analysis | YES | Y/x/? | |
| 5. Unsolicited Deposit Analysis | YES | Y/x/? | |
| 5b. Unsolicited Transfer Matrix (All Types) | **YES** | Y/x/? | **MANDATORY** -- never skip (R11) |
| 6. Token Type Confusion | YES | Y/x/? | |
| 7. Dispatchable Hook Impact | IF dispatchable tokens | Y/x(N/A)/? | |
| 8. Zero-Value Operations | YES | Y/x/? | |
| 9. Cross-Token Interactions | IF multi-token | Y/x(N/A)/? | |
| 10. Token Flow Checklist | YES | Y/x/? | |

### Cross-Reference Markers

**After Section 5** (Unsolicited Deposit Analysis):
- IF unsolicited deposits possible -> **MUST complete Section 5b with ALL 5 R11 dimensions**
- IF FungibleAsset with dispatchable hooks -> **MUST complete Section 7**

**After Section 6** (Token Type Confusion):
- IF protocol handles both Coin<T> and FA for same underlying -> **MUST check double-counting in Section 9**
- Cross-reference with `ZERO_STATE_RETURN.md` for first-depositor amplification via unsolicited deposits

**After Section 7** (Dispatchable Hook Impact):
- IF hooks can abort -> trace all callers for uncaught abort impact
- IF hooks have side effects -> trace through to accounting consistency

## references/aptos/type-safety.md

---
name: "type-safety"
description: "Trigger Pattern Always (Aptos Move) - generic type exploitation - Inject Into Breadth agents, depth-state-trace"
---

# TYPE_SAFETY Skill

> **Trigger Pattern**: Always (Aptos Move) --- generic type exploitation
> **Inject Into**: Breadth agents, depth-state-trace

Move's type system is its primary security mechanism. Generic type parameters allow modules to be polymorphic, but incorrect or insufficient type constraints enable attackers to substitute unexpected types, bypass access control, confuse token types, or exploit phantom type assumptions. This skill audits every generic interface for type safety violations.

**STEP PRIORITY**: Steps 2 (Type Parameter Substitution) and 5 (Coin/FungibleAsset Type Confusion) are where HIGH/CRITICAL severity findings most commonly hide. Do NOT rush these steps. If constrained, skip conditional sections (3, 4) before skipping 2 or 5.

## 1. Generic Function Inventory

Enumerate ALL public, public(friend), and entry functions with generic type parameters:

| Function | Module | Type Params | Constraints | Visibility | Entry? | Who Can Call |
|----------|--------|-------------|-------------|-----------|--------|-------------|
| `withdraw<T>` | vault | T | `key` | public | YES | Any signer |
| `swap<X, Y>` | dex | X, Y | `store` | public | YES | Any signer |

**MANDATORY GREP**: Search all `.move` files for `fun .*<` to find every generic function. Include internal (`fun`), `public(friend) fun`, `public fun`, and `public entry fun`.

For each generic function, additionally note:
- Does the function create, destroy, or transfer instances of the generic type?
- Does the function make assumptions about the generic type beyond its constraints? (e.g., assuming T is a coin type when the constraint is only `store`)
- Is the generic parameter used as a phantom/tag or does the function operate on actual instances of T?

## 2. Type Parameter Substitution Analysis

For each generic function identified in Step 1, analyze what happens when an attacker substitutes an unexpected type:

### 2a. Substitution Attack Table

| Function | Type Param | Expected Type | Attacker Substitutes | Guard Against Wrong Type? | Impact |
|----------|-----------|---------------|---------------------|--------------------------|--------|
| `withdraw<T>(store)` | T | RealCoin | FakeCoin (attacker-defined) | YES --- {mechanism} / NO | {impact} |

**Attack methodology per function**:

1. **Identify expected type**: What type does the protocol developer intend callers to use? This is often documented but NOT enforced at the type level.
2. **Check enforcement**: Is there an on-chain mechanism that restricts T to the expected type? Common mechanisms:
   - Registered type list (module stores `TypeInfo` and checks against it)
   - Type witness parameter (function also requires `&TypeWitness<T>`)
   - Module-level resource check (`assert!(exists<Pool<T>>(@protocol), E_INVALID_TYPE)`)
   - `coin::is_coin_initialized<T>()` check
   - Signer-of-defining-module pattern (only the module that defines T can call)
3. **If NO enforcement**: What happens if attacker creates `module attacker::fake { struct FakeCoin has store {} }` and calls `withdraw<FakeCoin>()`?

### 2b. Cross-Pool / Cross-Market Type Confusion

For protocols with pools, markets, or vaults parameterized by type:

| Pool/Market | Type Parameter | Can Attacker Create Pool With Arbitrary Type? | Impact If Confusion |
|-------------|---------------|----------------------------------------------|---------------------|
| `Pool<T>` | T | YES --- anyone can call `create_pool<T>()` / NO | {drain, mispricing, accounting error} |

**Check**: If Pool<RealCoin> and Pool<FakeCoin> exist, can operations on one affect the other? Common issues:
- Shared global state accessed by both pools
- Price oracle shared between pools (attacker manipulates FakeCoin price, affects RealCoin pool)
- Reward distribution computed across all pools regardless of type

Tag: `[TRACE:substitute T=FakeCoin → {function} → {bypass/confusion} → impact: {X}]`

## 3. Phantom Type Audit

For structs with phantom type parameters (`phantom T`):

### 3a. Phantom Type Inventory

| Struct | Phantom Param | Purpose | Runtime Impact of T | Can T Be Forged? |
|--------|--------------|---------|--------------------|--------------------|
| `Pool<phantom CoinType>` | CoinType | Type-tag discrimination | None (phantom) | {analysis} |

**Phantom type rules in Move**:
- Phantom type parameters do NOT affect runtime representation --- two structs with different phantom types have the same memory layout.
- Phantom types are used for type-level tagging: `Pool<USDC>` vs `Pool<WETH>` are different types at the Move level but identical at the bytecode level.
- The compiler enforces that phantom types are not used in non-phantom positions.

**Check for each phantom type**:
1. Is the phantom parameter used ONLY for type discrimination (correct use)?
2. Does any function extract or operate on the phantom type at runtime? (should be impossible by compiler, but verify no workarounds)
3. Can an attacker create a struct with a phantom type that aliases an existing legitimate phantom type? (e.g., creating `Pool<AttackerCoin>` that interacts with `Pool<USDC>` state)
4. Are phantom type parameters properly propagated through nested generics? (`Wrapper<phantom T>` containing `Inner<T>` --- is T phantom in Inner too?)

### 3b. Phantom Type Bypass Patterns

| Pattern | Risk | Check |
|---------|------|-------|
| Phantom used for access control | Medium | Can attacker define their own type to bypass access gate? |
| Phantom used for pool isolation | High | Does pool isolation rely solely on phantom type discrimination? |
| Phantom type in event emission | Low | Can attacker emit events with spoofed phantom types for off-chain confusion? |

## 4. Type Witness Pattern

For functions that accept type witnesses:

### 4a. Witness Inventory

| Witness Struct | Creating Module | Who Can Create? | Functions That Accept It | Properly Gated? |
|---------------|----------------|----------------|------------------------|-----------------|
| `TypeWitness<T>` | {module} | {analysis} | {list} | YES/NO |

**Type witness pattern** is Move's equivalent of capability-based access control at the type level. A type witness is a struct that can only be created by the module that defines the associated type. Functions that require a witness parameter are restricted to callers authorized by that module.

**Check for each witness**:
1. Is the witness struct defined in the SAME module as the type it witnesses? If not, the witness can be created by anyone who imports the witness module.
2. Does the witness have `drop`? If yes, it can be created once and reused --- is this intended?
3. Does the witness have `copy`? If yes, it can be duplicated --- does this break single-use assumptions?
4. Does the witness have `store`? If yes, it can be persisted --- can an attacker store a witness and replay it later?
5. Is witness creation gated by signer checks or capability pattern? Or can any function in the defining module create it?

### 4b. Witness Forgery Analysis

For each witness used for access control:

```
1. TypeWitness<T> is required by function F
2. TypeWitness<T> can be created by: {list of functions/modules}
3. Can attacker reach a creation path? {YES/NO --- trace}
4. If YES: attacker creates witness and calls F with unauthorized type T
5. Impact: {unauthorized operation}
```

## 5. Coin/FungibleAsset Type Confusion

For all functions that handle `Coin<T>` or `FungibleAsset`:

### 5a. Coin Type Enforcement

| Function | Accepts | Type Restriction | Enforcement Mechanism | Bypass Possible? |
|----------|---------|-----------------|----------------------|-----------------|
| `deposit<T>(coin: Coin<T>)` | Coin<T> | T must be registered | `assert!(is_registered<T>())` | {analysis} |

**Check for each coin-handling function**:
1. Does the function verify that T is the expected coin type? Or does it accept ANY `Coin<T>`?
2. If the function interacts with a pool/vault typed by T, does it verify the coin type matches the pool type?
3. Can an attacker deposit `Coin<FakeCoin>` and withdraw `Coin<RealCoin>`?
4. For multi-coin functions (`swap<X, Y>`): are X and Y validated to be a supported pair? Can attacker swap between arbitrary types?

### 5b. FungibleAsset Metadata Confusion

For protocols using the Aptos Fungible Asset standard:

| Function | Metadata Check | Object Address Validated? | Impact If Wrong Metadata |
|----------|---------------|--------------------------|--------------------------|
| {function} | YES --- `assert!(metadata == expected)` / NO | YES/NO | {wrong asset deposited/withdrawn} |

**Aptos FA-specific checks**:
1. `FungibleAsset` is NOT parameterized by type --- it uses a metadata object address for discrimination. This means type-level enforcement does NOT apply. All FungibleAssets have the same Move type.
2. Does the function verify the metadata object matches the expected asset? If not, any FungibleAsset can be passed.
3. Can an attacker create a FungibleAsset with spoofed metadata (same name/symbol as a legitimate asset)?
4. Are `FungibleStore` addresses properly validated when reading balances?

Tag: `[TRACE:deposit FakeCoin to Pool<RealCoin> → withdraw RealCoin → drain pool]`

### 5c. Mixed Standard Confusion

For protocols that handle BOTH `Coin<T>` and `FungibleAsset`:

| Operation | Which Standard Used? | Consistent? | Can Attacker Force Wrong Standard? |
|-----------|---------------------|-------------|-----------------------------------|
| deposit | Coin<T> | --- | --- |
| withdraw | FungibleAsset | MISMATCH | {analysis} |

**Check**: If deposit uses `Coin<T>` but withdraw uses `FungibleAsset` (or vice versa), is the accounting consistent? The Aptos framework provides conversion between `Coin<T>` and `FungibleAsset`, but the conversion path may not be symmetric or may bypass module-level accounting.

## 6. Module Type Authority

Only the module that defines a struct can create instances of it. This is Move's module encapsulation guarantee. Audit for violations:

### 6a. Instance Creation Audit

| Struct | Defining Module | Public Functions That Return New Instances | Should Creation Be Public? |
|--------|----------------|------------------------------------------|--------------------------|
| {name} | {module} | {list or NONE} | YES/NO --- {reason} |

**Check for each struct**:
1. Does ANY `public` or `public(friend)` function return a newly created instance of this struct?
2. If yes, should external callers be able to obtain new instances? For value-bearing structs (coins, shares, receipts), uncontrolled creation = minting vulnerability.
3. For structs used as capabilities or proofs: is there a public function that creates and returns them to arbitrary callers?
4. For `friend` functions that create instances: are ALL friend modules trusted to create instances responsibly?

### 6b. Friend Module Trust Analysis

| Module | Friend Modules | What Friends Can Create | Trust Justified? |
|--------|---------------|------------------------|-----------------|
| {module} | {friends list} | {structs accessible via friend functions} | {analysis} |

**Check**: The `friend` declaration grants the friend module full access to `public(friend)` functions, including creation functions. If a friend module has a vulnerability, it can be used to mint/create unauthorized instances.

**MANDATORY**: For each friend relationship, verify: if the friend module is compromised (has a vulnerability), what is the maximum damage to the declaring module? If friend can create value-bearing structs → severity minimum HIGH.

## Finding Template

When this skill identifies an issue:

```markdown
**ID**: [TS-N]
**Severity**: [based on type confusion impact --- fund loss from wrong type = Critical]
**Step Execution**: check1,2,3,4,5,6 | X(reasons) | ?(uncertain)
**Rules Applied**: [R1:Y, R4:Y, R5:Y, R10:Y]
**Depth Evidence**: [TRACE:substitute T=X → bypass → impact], [BOUNDARY:type=FakeCoin]
**Location**: module::function (source_file.move:LineN)
**Title**: [Function] accepts arbitrary type [T] without [validation], enabling [type confusion/drain/forgery]
**Description**: [Trace from attacker type substitution through function logic to impact]
**Impact**: [Fund drain, unauthorized minting, accounting corruption, pool confusion]
```

---

## Step Execution Checklist (MANDATORY)

> **CRITICAL**: You MUST report completion status for ALL sections. Steps 2 and 5 are highest priority.

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Generic Function Inventory | **YES** | Y/X/? | **MANDATORY** --- grep ALL .move files |
| 2. Type Parameter Substitution | **YES** | Y/X/? | **MANDATORY** --- highest-severity source |
| 2b. Cross-Pool Type Confusion | IF pools/markets parameterized by type | Y/X(N/A)/? | |
| 3. Phantom Type Audit | IF phantom types used | Y/X(N/A)/? | |
| 4. Type Witness Pattern | IF witness pattern used | Y/X(N/A)/? | |
| 5. Coin/FA Type Confusion | **YES** | Y/X/? | **MANDATORY** --- fund loss vector |
| 5b. FungibleAsset Metadata | IF FA standard used | Y/X(N/A)/? | Metadata validation |
| 5c. Mixed Standard Confusion | IF both Coin and FA used | Y/X(N/A)/? | |
| 6. Module Type Authority | YES | Y/X/? | Creation function audit |
| 6b. Friend Module Trust | IF friend declarations exist | Y/X(N/A)/? | Friend compromise analysis |

### Cross-Reference Markers

**After Section 2** (Type Parameter Substitution):
- IF type substitution enables unauthorized access -> cross-reference with `ABILITY_ANALYSIS.md` Section 7 for ability constraint gaps
- IF no enforcement mechanism found -> severity minimum HIGH for value-handling functions

**After Section 5** (Coin/FA Type Confusion):
- Cross-reference with token flow analysis for entry/exit point type validation
- IF FungibleAsset used without metadata check -> severity minimum HIGH (any FA can be deposited)
- IF mixed Coin + FA standards -> verify accounting consistency across standards

**After Section 6** (Module Type Authority):
- IF public creation function for value-bearing struct -> severity minimum CRITICAL (unauthorized minting)
- IF friend module has known vulnerability -> escalate all friend-accessible creation to HIGH minimum

### Mandatory Forced Output

For Sections 2 and 5, you MUST produce output even if no issues found:

**Section 2 Output** (always required):
```markdown
### 2. Type Parameter Substitution Analysis
| Function | Type Param | Expected Type | Enforcement | Substitution Blocked? |
|----------|-----------|---------------|-------------|----------------------|
| {function} | T | {expected} | {mechanism or NONE} | YES/NO |

**If enforcement = NONE for any value-handling function**: Finding verdict minimum PARTIAL.
```

**Section 5 Output** (always required):
```markdown
### 5. Coin/FungibleAsset Type Confusion
| Function | Standard | Type Restriction | Enforcement | Bypass Possible? |
|----------|---------|-----------------|-------------|-----------------|
| {function} | Coin/FA | {restriction} | {mechanism} | YES/NO |

**If ANY coin-handling function lacks type enforcement**: Verdict CONFIRMED, severity based on fund impact.
```

## references/aptos/verification-protocol.md

---
name: "verification-protocol"
description: "How to prove a hypothesis is TRUE or FALSE using Move unit tests."
---

# Verification Protocol -- Aptos Move

> How to prove a hypothesis is TRUE or FALSE using Move unit tests.

---

## Evidence Source Tracking (MANDATORY)

> **CRITICAL**: For EVERY piece of evidence used in verification, you MUST tag its source. Evidence from mocks or unverified external modules CANNOT support a REFUTED verdict.

### Evidence Source Tags

| Tag | Meaning | Valid for REFUTED? |
|-----|---------|-------------------|
| [PROD-ONCHAIN] | Production module verified on Aptos Explorer | YES |
| [PROD-SOURCE] | Source code verified on-chain (Aptos Explorer source verification) | YES |
| [CODE] | Audited codebase (in-scope) | YES |
| [MOCK] | Mock/test module | **NO** |
| [EXT-UNV] | External module, unverified behavior | **NO** |
| [DOC] | Documentation/spec only | NO (needs verification) |

### Evidence Audit Table (REQUIRED in every verification output)

Before ANY verdict, fill this table:

```markdown
### Evidence Audit
| Claim | Evidence Source | Tag | Valid for REFUTED? |
|-------|-----------------|-----|-------------------|
| "External module returns X" | Mock module | [MOCK] | NO |
| "State changes to Y" | protocol_module.move:123 | [CODE] | YES |
| "Coin transfer triggers Z" | Aptos Explorer source | [PROD-ONCHAIN] | YES |
```

### Mock Rejection Rule

**AUTOMATIC OVERRIDE**: If ANY evidence supporting REFUTED has tag [MOCK] or [EXT-UNV]:
- CANNOT return REFUTED
- MUST return CONTESTED
- Triggers production verification

**Example**:
```markdown
## Verdict: REFUTED -> CONTESTED (mock evidence override)

### Evidence Audit
| Claim | Source | Tag | Valid? |
|-------|--------|-----|--------|
| "Staking returns shares" | test_staking.move:45 | [MOCK] | NO |

**Override reason**: REFUTED verdict relies on mock behavior at test_staking.move:45.
Production module behavior is UNVERIFIED. Must verify against on-chain source.
```

---

## Pre-Verification Understanding

Before writing ANY test code, you MUST answer:

### Question 1: What is the EXACT bug?
```
NOT: "Something is inconsistent"
NOT: "State is wrong"
NOT: "Capability leak possible"

YES: "[Variable/resource] is [read/written/moved] at [location] but should be
      [read/written/moved] at [other location] because [specific reason]"
```

### Question 2: What OBSERVABLE difference proves it?
```
NOT: "Values are different"
NOT: "State changed"

YES: "Before operation: [resource/value] = [expected value]
      After operation: [resource/value] = [actual value]
      Expected: [what it should be]"
```

### Question 3: What is the EXACT assertion?
```
NOT: assert!(bug_exists, 0)
NOT: assert!(!is_secure, 0)

YES: assert!(actual_value == expected_value, ERROR_CODE)
 OR: assert!(before != after, ERROR_CODE)  // "value changed when it shouldn't"
 OR: assert!(error > threshold, ERROR_CODE)  // "error exceeds acceptable threshold"
```

**If you cannot answer all three -> ASK FOR CLARIFICATION**

---

## Pre-PoC Feasibility Gates (MANDATORY)

Before writing test code, verify these two gates. If either FAILS, adjust the hypothesis.

### Gate F1: Reachability
Trace a call path from a permissionless entry point to the vulnerable code.

- [ ] Entry point identified (public/external/entry function)
- [ ] Call path traced through intermediary functions
- [ ] All access checks on the path are passable by the attacker profile

If NO entry point reaches the vulnerable code → UNREACHABLE → FALSE_POSITIVE.
If reachable only through a restricted path → document the restriction, adjust likelihood.

### Gate F2: Math Bounds
Substitute real-world value domains into the expression that triggers the bug.

- [ ] Parameter domains identified (token decimals, max supply, TVL range, fee range, time bounds)
- [ ] Expression evaluated at worst-case feasible inputs
- [ ] Result crosses the bug threshold

If the bug requires values outside feasible domains → INFEASIBLE → FALSE_POSITIVE.
If feasible only at extreme but realistic parameters → document the threshold, proceed with adjusted severity.

**Both gates PASS → proceed to PoC. Either gate FAILS → document and stop.**

---


## Test File Template

> **See [`templates.md`](references/templates.md)** in this directory for all Move test file templates and Move-specific test patterns.

## Interpreting Results

### Test PASSES -> Bug CONFIRMED
The assertion that "proves the bug" succeeded.
- If `assert!(after != before, 0)` passes -> values ARE different (bug exists)
- If `assert!(error > threshold, 0)` passes -> error IS above threshold (bug exists)

### Test FAILS -> Check Why

| Failure | Meaning | Action |
|---------|---------|--------|
| Assertion failed (abort code) | Bug doesn't exist as hypothesized | Re-examine hypothesis |
| Abort in setup | Module initialization wrong | Fix setup (check init order, missing resources) |
| Abort in action | Operation blocked (access control, precondition) | Check preconditions, signer requirements |
| ARITHMETIC_ERROR (0x20001) | Overflow/underflow or division by zero | Check calculations, validate inputs |
| RESOURCE_NOT_FOUND | Missing `move_to` in setup | Ensure all required resources are initialized |
| ALREADY_EXISTS | Duplicate resource creation | Check init called only once |

### Common Aptos-Specific Test Issues

| Issue | Cause | Fix |
|-------|-------|-----|
| `ENOT_FOUND` on coin operations | Account not registered for coin type | Add `coin::register<CoinType>(user)` before operations |
| Timestamp not available | `timestamp` module not initialized | Add `timestamp::set_time_has_started_for_testing(aptos_framework)` |
| Object not found | Object created at unexpected address | Use `object::create_named_object` with deterministic seed |
| Module not published | Test module can't import protocol module | Check `Move.toml` dependencies and test address mapping |
| Signer mismatch | `@protocol_addr` doesn't match expected | Verify `#[test(...)]` signer addresses match module publish address |

---

## Iteration Protocol

**Attempt 1:** Direct implementation of test strategy from hypothesis

**Attempt 2:** Adjust parameters
- Different amounts (larger/smaller, boundary values)
- Different timing (advance more/fewer seconds)
- Different actors (swap attacker/victim roles)
- Different resource initialization order

**Attempt 3:** Re-examine assumptions
- Is setup correct? (all resources initialized, correct init order)
- Are preconditions met? (correct signer, sufficient balance, required state)
- Is the bug mechanism correctly understood?
- Are module dependencies correctly configured in Move.toml?

**After 5 attempts:**
- If still fails -> FALSE_POSITIVE with documented reasoning
- Explain why the hypothesis was wrong

---

## Severity Determination

### CRITICAL
- Direct fund theft possible (drain FungibleStore, mint unlimited tokens)
- Protocol insolvency (assets < liabilities)
- No special prerequisites needed (permissionless exploit)
- Attacker profits significantly
- Ref capability leak granting unrestricted mint/transfer/burn

### HIGH
- Fund loss with some setup (specific state required)
- Broken core functionality (deposits, withdrawals, swaps non-functional)
- Significant value at risk
- Cumulative error compounds quickly
- Ref capability leak with limited but significant blast radius

### MEDIUM
- Limited fund loss (bounded by rate limits, caps)
- Requires specific conditions (timing, state, multi-step)
- Edge cases with real impact
- Moderate value at risk
- Access control weakness that requires compromised friend module

### LOW
- Negligible direct impact
- Extreme edge cases only
- Admin/owner controlled risk with compensating controls
- Informational with minor consequence

---

## Output Format

### CONFIRMED

```markdown
## Verdict: CONFIRMED

### Evidence Audit
| Claim | Evidence Source | Tag | Valid for REFUTED? |
|-------|-----------------|-----|-------------------|

### Bug Mechanism Verified
{Explain what the test proves in 2-3 sentences}

### Test File
`tests/audit/test_hypothesis_N.move`

### Test Output
```
{Paste relevant `aptos move test` output}
```

### Key Evidence
| Metric | Value |
|--------|-------|
| Before | {value} |
| After | {value} |
| Expected | {value} |
| Difference | {calculation} |

### Severity: {LEVEL}
{Justification in 1-2 sentences}

### RAG Evidence
- **Attack Vectors Consulted**: [list bug classes queried]
- **Similar Exploits Found**: [count and brief descriptions]
- **PoC Template Used**: [yes/no, which template]
- **Historical Precedent**: [describe any matching historical vulnerabilities]
```

### FALSE_POSITIVE

```markdown
## Verdict: FALSE_POSITIVE

### Evidence Audit
| Claim | Evidence Source | Tag | Valid for REFUTED? |
|-------|-----------------|-----|-------------------|

### Attempts Made

**Attempt 1:**
- Approach: {description}
- Result: {what happened}
- Learning: {insight}

**Attempt 2:**
- Approach: {description}
- Result: {what happened}
- Learning: {insight}

**Attempt 3:**
- Approach: {description}
- Result: {what happened}
- Learning: {insight}

### Why It's Not a Bug
{Explain the actual behavior and why hypothesis was wrong in 2-3 sentences}
```

### CONTESTED (CRITICAL)

```markdown
## Verdict: CONTESTED

### Evidence Audit
| Claim | Evidence Source | Tag | Valid for REFUTED? |
|-------|-----------------|-----|-------------------|

### Evidence Status
| Checkpoint | Status | Details |
|------------|--------|---------|
| External behavior verified against PRODUCTION | NO | Used mock behavior as evidence |
| All callers checked | YES | Checked A, B, C |
| Ref access paths fully traced | NO | Friend module re-export not analyzed |
| Profit calculated with attacker holding | NO | Only analyzed donation loss |

### Why This Cannot Be REFUTED
{Explain what evidence is missing to definitively rule out the bug}

### Escalation Required
- [ ] Fetch production module source from Aptos Explorer for {external dep}
- [ ] Re-analyze with attacker holding shares/tokens
- [ ] Check additional caller paths: {list}
- [ ] Trace Ref access through friend modules: {list}

### Current Assessment
Likely: {TRUE_POSITIVE / FALSE_POSITIVE / UNKNOWN}
Confidence: {LOW / MEDIUM}
```

---

## Insufficient Evidence (HALT CONDITIONS) -- CRITICAL

> **MANDATORY**: You MUST check ALL boxes before returning REFUTED.
> If ANY checkbox is NO -> Return CONTESTED, not REFUTED.

Before marking REFUTED, check:
- [ ] External behavior verified against PRODUCTION (not mock)
  - Check Aptos Explorer for on-chain module source verification
  - If external module is marked 'UNVERIFIED' -> CANNOT use as evidence
  - If mock differs from production -> use PRODUCTION behavior
- [ ] Attack path checked on ALL callers (not just main path)
  - Enumerate all `public fun` and `public entry fun` that reach the vulnerable code
  - Check `public(friend) fun` callers via friend module analysis
- [ ] Ref capability paths fully traced
  - For Ref-related findings: trace every path from Ref creation to Ref usage
  - Check friend modules for transitive Ref access
  - Check if ExtendRef-derived signer enables unexpected access
- [ ] Profit calculated with attacker HOLDING tokens (not just donating)
  - "Attacker loses by donating" is NOT sufficient evidence
  - Check: what if attacker holds X% of shares BEFORE donating?
- [ ] **Missing precondition documented**
  - Document in structured format: precondition type + why it blocks
  - Types: STATE / ACCESS / TIMING / EXTERNAL / BALANCE
- [ ] **Searched other findings for matching postconditions**
  - Read `{scratchpad}/findings_inventory.md` for CONFIRMED/PARTIAL findings
  - Check if ANY finding creates the postcondition that would enable this attack
  - If match found -> CONTESTED, not REFUTED (chain analysis will combine)

### Evidence That Does NOT Count
- "Mock shows X" -- mocks != production (CRITICAL: always verify against production)
- "Standard Coin module" -- may have custom transfer hooks via fungible_asset dispatch
- "Attacker loses by donating" -- may profit via shares held
- "Function is private/friend" -- friend module may expose it publicly
- "Requires admin signer" -- admin may be compromised or malicious
- "Attacker cannot acquire X" -- another finding may CREATE this condition
- "Ref is in private storage" -- friend module may provide access path

### Anti-Downgrade Halt for VS/BLIND Findings (HARD RULE)
For findings from Validation Sweep ([VS-*]) or Blind Spot Scanner ([BLIND-*]): apply Rule 13's 5-question test BEFORE any downgrade.
**HALT**: If test shows users harmed AND unavoidable AND undocumented -> you CANNOT return FALSE_POSITIVE. Minimum verdict: CONTESTED.
Defense parity gaps (Module A has protection X, Module B lacks it for same action) are NEVER "by design" -> minimum severity: Medium, minimum verdict: CONTESTED.
Violating this halt is a workflow error equivalent to using [MOCK] evidence for REFUTED.

### Chain Analysis Integration

A finding is NEVER truly REFUTED until chain analysis completes.

If you mark a finding as REFUTED but document a missing precondition, the chain analyzer
will search for other findings whose postconditions match your missing precondition.
If found, the finding will be escalated to CONTESTED and combined into a chain hypothesis.

**Example**:
- Your finding: "Drain attack blocked because attacker cannot get TransferRef"
- Other finding: "Friend module exposes TransferRef via public function"
- Chain: Other finding enables your finding -> Combined HIGH severity

---



---

> **Advanced Protocol Reference**: See [`advanced.md`](references/advanced.md) for RAG queries before PoC, exchange rate finding severity, design flaw escalation, bidirectional role analysis, chain hypothesis, and Aptos-specific verification considerations.

## references/aptos/zero-state-return.md

---
name: "zero-state-return"
description: "Trigger Pattern Vault/pool/first-depositor pattern detected - Inject Into Depth-edge-case"
---

# ZERO_STATE_RETURN Skill

> **Trigger Pattern**: Vault/pool/first-depositor pattern detected
> **Inject Into**: Depth-edge-case
> **Purpose**: Analyze zero-state transitions in Aptos Move protocols -- initial zero state, return to zero after operations, residual assets, and re-entry vulnerabilities

## Overview

This skill covers BOTH initial zero state AND return-to-zero-state analysis:
- Protocol initialization and first deposit conditions
- Protocol returning to zero after normal operations
- Residual assets when supply returns to zero
- Re-entry vulnerabilities after full exit

## 1. Identify Zero-State Transitions

Find all vault/pool/staking mechanisms and their zero-state boundaries:

| State | Resource / Variable | Zero Condition | Trigger | Code Location |
|-------|-------------------|----------------|---------|---------------|
| Total shares | {resource.total_supply} | `== 0` | All users withdrew/burned | {module:line} |
| Total assets | {resource.total_assets} | `== 0` | No funds deposited | {module:line} |
| Pool liquidity | {resource.reserves} | Both reserves `== 0` | All LP withdrawn | {module:line} |
| Staking pool | {resource.total_staked} | `== 0` | All unstaked | {module:line} |

For each state: what is the protocol behavior when this condition is true?

## 2. First Depositor Analysis

Can the first depositor manipulate share price?

### 2a. Share Minting Formula at Zero State

| Protocol | Formula | When totalShares == 0 | First Deposit Behavior |
|----------|---------|----------------------|----------------------|
| {name} | `shares = amount * totalShares / totalAssets` | {special case?} | {describe} |

**Classic first depositor attack on Aptos**:
1. First depositor deposits minimal amount (e.g., 1 unit)
2. Attacker directly deposits tokens to the protocol's FungibleStore (unsolicited -- bypasses accounting)
3. Exchange rate inflates: `totalAssets` increases but `totalShares` stays at 1
4. Next depositor receives 0 shares due to rounding (their deposit amount < inflated share price)
5. First depositor withdraws, capturing the second depositor's funds

**Checks**:
- [ ] Is there a minimum first deposit requirement?
- [ ] Does the protocol use virtual shares/assets (e.g., add 1 to both numerator and denominator)?
- [ ] Is there a dead shares mechanism (burn initial shares to zero address)?
- [ ] Can unsolicited deposits to the protocol's store inflate `totalAssets`?
- [ ] Does the protocol use internal accounting (resistant) or direct balance queries (vulnerable)?

### 2b. First Deposit Protection Mechanisms

| Protection | Present? | Implementation | Bypass Possible? |
|-----------|----------|----------------|-----------------|
| Minimum first deposit | YES/NO | {code ref} | {analysis} |
| Virtual shares/assets offset | YES/NO | {code ref} | {analysis} |
| Dead shares (initial mint to zero) | YES/NO | {code ref} | {analysis} |
| Internal accounting (not balance-based) | YES/NO | {code ref} | {analysis} |
| Decimal offset in share calculation | YES/NO | {code ref} | {analysis} |

## 3. Return to Zero Analysis

After normal operations, can the protocol return to zero state?

### 3a. Return-to-Zero Scenarios

| Scenario | Trigger | Residual State After | Re-entry Safe? |
|----------|---------|---------------------|---------------|
| All shares redeemed | Last user withdraws | {what remains?} | YES/NO |
| Emergency withdraw | Admin drains | {what remains?} | YES/NO |
| All stakers unstake | Last unstake | {what remains?} | YES/NO |
| Pool fully drained | All LP removed | {what remains?} | YES/NO |

### 3b. Can Total Shares Reach Exactly Zero?

Trace the withdrawal/burn path:
- Can the last user withdraw ALL their shares? (no minimum balance lock?)
- Does the protocol enforce a minimum share amount that prevents reaching zero?
- If dead shares exist, `totalShares` never reaches 0 -- is this protection consistent?

## 4. Residual Asset Check

When supply returns to zero, check for stranded value:

### 4a. Accrued Rewards

| Reward Source | Persists When totalShares = 0? | Claimable By Next Depositor? | Amount Bounded? |
|-------------|-------------------------------|-----------------------------:|----------------|
| {reward_source} | YES/NO | YES/NO | {max amount or UNBOUNDED} |

If rewards persist AND next depositor can claim -> FINDING (severity based on amount).

### 4b. Unclaimed Fees

| Fee Type | Persists When totalShares = 0? | Captured By Next Depositor? | Reconciliation Mechanism? |
|----------|-------------------------------|----------------------------|--------------------------|
| {fee_type} | YES/NO | YES/NO | {mechanism or NONE} |

### 4c. Dust Balances

- Can dust (sub-unit amounts) remain in FungibleStore after all withdrawals?
- Does dust affect exchange rate calculations on re-entry? (e.g., `totalAssets = 1 wei, totalShares = 0`)
- Does the protocol handle `totalAssets > 0 AND totalShares == 0` explicitly?

### 4d. Pending Operations

- Are there pending withdrawals/claims that persist after zero state?
- What happens to in-flight multi-step operations when supply hits zero?
- Are there resources or objects that reference the pool/vault state that become orphaned?

## 5. Re-Entry Vulnerability Analysis

Does re-entering zero state recreate first-depositor attack conditions?

| Scenario | Initial State | Return-to-Zero State | Same Vulnerability? |
|----------|---------------|---------------------|---------------------|
| First depositor attack | totalSupply=0, totalAssets=0 | totalSupply=0, totalAssets=X (residual) | **WORSE** if residual > 0 |
| Exchange rate manipulation | No shares exist | No shares, but balance exists | YES + amplified |
| Donation attack | Clean state | Dirty state | YES + pre-seeded |

**Key question**: Is the first-deposit protection (from Section 2b) applied ONLY on initial deployment, or does it also trigger when `totalShares` returns to 0?

Trace the share minting code:
```
// Pattern: Protection covers initial AND return-to-zero
if (total_shares == 0) {
    // First deposit logic with protection
}

// vs Pattern: Protection only on first-ever deposit
if (!initialized) {
    // Protection here
} else if (total_shares == 0) {
    // NO protection -- vulnerable on return-to-zero
}
```

## 5b. Default/Uninitialized State Values

For each state field used in arithmetic or control flow, check its **initial value** before any user interaction:

- **Default zero**: Move initializes struct fields to their declared defaults (typically 0 for integers, `@0x0` for addresses). If a function uses `last_timestamp`, `start_time`, or `last_update` in subtraction or division BEFORE it has ever been set, the result may be unexpected (e.g., `timestamp::now_seconds() - 0` = enormous elapsed time, or division by a value derived from 0).
- **First-call path**: Trace the FIRST invocation of each state-modifying function. Does it assume a prior call already initialized dependent fields?
- **Check**: For each field read in a function, is there a code path where that field still holds its default value (0, @0x0, false)? If yes, does the function behave correctly with that default?

## 6. Empty Pool Edge Cases

### 6a. Division by Zero

| Expression | When totalShares = 0 | Behavior | Impact |
|-----------|---------------------|----------|--------|
| `amount * totalShares / totalAssets` | 0 / totalAssets | Returns 0 | {impact} |
| `amount * totalAssets / totalShares` | amount * X / 0 | **ABORT** | {DoS, broken withdrawal} |
| `rewards / totalShares` | rewards / 0 | **ABORT** | {reward distribution broken} |

For each division: is there a zero-check guard? If not, what transaction aborts?

### 6b. Zero-Amount Operations at Zero State

| Operation | At Zero State | Result | Expected? |
|-----------|--------------|--------|-----------|
| deposit(0) at totalShares=0 | {behavior} | {shares issued?} | {analysis} |
| withdraw(0) at totalShares=0 | {behavior} | {aborts?} | {analysis} |
| claim_rewards() at totalShares=0 | {behavior} | {rewards distributed?} | {analysis} |

## 7. Protocol Reset Functions

Check for admin functions that can force zero state:

| Function | Access Control | Clears All State? | Residual After Reset |
|----------|---------------|-------------------|---------------------|
| {emergency_withdraw_fn} | {who} | YES/NO | {what remains} |
| {rescue_tokens_fn} | {who} | YES/NO | {what remains} |
| {pause + drain_fn} | {who} | YES/NO | {what remains} |
| {migrate_fn} | {who} | YES/NO | {what remains in old module} |

For each: what state persists after the "reset"? Can it be exploited?

## Instantiation Parameters
```
{CONTRACTS}              -- Move modules containing vault/pool logic
{SHARE_VARIABLES}        -- Variables tracking total shares/supply
{ASSET_VARIABLES}        -- Variables tracking total assets/deposits
{SHARE_MINT_FORMULA}     -- Share calculation formula at deposit
{FIRST_DEPOSIT_GUARDS}   -- Existing first-deposit protections
```

## Finding Template

```markdown
**ID**: [ZS-N]
**Severity**: [typically HIGH if funds extractable, MEDIUM if DoS]
**Step Execution**: checkmark1,2,3,4,5,6,7 | x(reasons) | ?(uncertain)
**Rules Applied**: [R4:Y, R10:Y, R11:Y]
**Location**: module::function:LineN
**Title**: [Zero-state type] allows [attack] due to [residual state / missing protection]
**Description**:
- Protocol can reach totalShares=0 via [mechanism]
- When this happens, [state variable] retains value of [amount]
- A new depositor can [exploit path]
**Impact**: [Fund extraction / exchange rate manipulation / DoS]
```

## Output Schema

| Field | Required | Description |
|-------|----------|-------------|
| zero_state_transitions | yes | All paths to zero state |
| first_depositor_analysis | yes | First deposit attack assessment |
| residual_assets | yes | What persists after zero state |
| re_entry_vulnerability | yes | Whether return-to-zero recreates first-depositor conditions |
| edge_cases | yes | Division by zero and zero-amount operations |
| finding | yes | CONFIRMED / REFUTED / CONTESTED |
| evidence | yes | Code locations with line numbers |
| step_execution | yes | Status for each step |

---

## Step Execution Checklist (MANDATORY)

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Identify Zero-State Transitions | YES | Y/x/? | |
| 2. First Depositor Analysis | YES | Y/x/? | Including 2a formula + 2b protections |
| 3. Return to Zero Analysis | YES | Y/x/? | Including 3a scenarios + 3b exact zero trace |
| 4. Residual Asset Check | YES | Y/x/? | All sub-checks: 4a rewards, 4b fees, 4c dust, 4d pending |
| 5. Re-Entry Vulnerability Analysis | YES | Y/x/? | Compare initial vs return-to-zero protections |
| 6. Empty Pool Edge Cases | YES | Y/x/? | Division by zero + zero-amount ops |
| 7. Protocol Reset Functions | IF admin reset exists | Y/x(N/A)/? | |

### Cross-Reference Markers

**After Section 2** (First Depositor): Cross-reference with `TOKEN_FLOW_TRACING.md` Section 5 for unsolicited deposit vectors that amplify first-depositor attacks.

**After Section 4** (Residual Assets): If residual rewards/fees found, cross-reference with `ECONOMIC_DESIGN_AUDIT.md` for whether fee/reward accumulation is bounded.

**After Section 5** (Re-Entry): If return-to-zero is possible AND first-deposit protection is initial-only -> FINDING (minimum Medium, upgrade to High if unsolicited deposits can amplify).

## references/common

```

```

## references/common/move-language.md

# Move Language Reference

This document provides a comprehensive reference for the Move programming language as used in smart contract development on Sui and Aptos.

---

## 1. Module System

Move code is organized into **modules** — the fundamental unit of code organization, similar to contracts in Solidity.

```move
module package_name::module_name {
    // Structs, functions, constants live here
}
```

### Module Rules

- Each module is defined in its own `.move` file
- Module name must match the file name
- A **package** (Move.toml + sources/) contains multiple modules
- Modules can import other modules with `use`

```move
module my_package::coin {
    use sui::coin::{Self, Coin};
    use sui::tx_context::TxContext;

    public struct MYCOIN has drop {} // One-time witness

    fun init(witness: MYCOIN, ctx: &mut TxContext) {
        let (treasury_cap, coin_metadata) = coin::create_currency(
            witness,
            6,           // decimals
            b"MYC",      // symbol
            b"MyCoin",   // name
            b"A test coin",
            option::none(),
            ctx,
        );
        transfer::public_freeze_object(coin_metadata);
        transfer::public_transfer(treasury_cap, tx_context::sender(ctx));
    }
}
```

---

## 2. Ability System

Move uses **abilities** to control struct behavior. Four abilities exist:

| Ability | Keyword | Effect |
|---------|---------|--------|
| Copy | `copy` | Values can be duplicated |
| Drop | `drop` | Values can be implicitly discarded |
| Key | `key` | Values can be stored globally (as a key) |
| Store | `store` | Values can be stored inside other structs or in global storage |

### Ability Declaration

```move
// Asset type: no copy, no drop — must be explicitly handled
public struct Coin has key, store {
    id: UID,
    value: u64,
}

// Witness type: only drop — created once, consumed immediately
public struct WITNESS has drop {}

// Capability: key only — stored as a global object
public struct AdminCap has key {
    id: UID,
}

// Data type: all abilities — freely copy and discard
public struct Config has copy, drop, store {
    fee_rate: u64,
    paused: bool,
}
```

### Ability Implications for Security

| Pattern | Risk | Severity |
|---------|------|----------|
| Asset with `copy` | Funds can be duplicated | CRITICAL |
| Asset with `drop` | Funds can be silently lost | CRITICAL |
| Witness with `store` | Can be stored and reused | HIGH |
| Witness with `copy` | Can be duplicated | HIGH |
| Capability with `store` | Can be stored in other objects | Review needed |

### Phantom Types

The `phantom` keyword declares type parameters that don't affect the struct's abilities:

```move
// phantom T is only used as a type marker, not stored
public struct Coin<phantom T> has key, store {
    id: UID,
    value: u64,
}

// Without phantom, T would need to satisfy store ability
// With phantom, Coin<Sui> and Coin<USDC> are different types
// but T doesn't need any abilities
```

---

## 3. Resource Model

Move's resource model is its defining feature: **resources cannot be copied or dropped**. This is enforced at the compiler level.

### Struct Unpacking

```move
// Resources must be explicitly destructured
public entry fun burn(coin: Coin<T>) {
    let Coin { id, value: _ } = coin;
    object::delete(id);
}
```

### Transfer Patterns

```move
// Sui: object transfer
transfer::public_transfer(obj, recipient);
transfer::public_share_object(obj);
transfer::freeze_object(obj);

// Aptos: move_to signer-based storage
move_to<T>(signer, resource);
```

---

## 4. Functions and Visibility

### Visibility Levels

```move
// Private — only callable within this module
fun helper() { }

// Public — callable from any module
public fun get_value(): u64 { }

// Public entry — callable from transactions AND modules
public entry fun user_action(ctx: &mut TxContext) { }

// Entry only — callable from transactions, NOT from modules
entry fun transaction_only(account: &signer) { }

// Sui: package-visible
public(package) fun internal() { }

// Aptos: friend-visible
public(friend) fun for_friends() { }
```

### Function Parameters

```move
// Sui entry functions use TxContext
public entry fun create_object(ctx: &mut TxContext) { }

// Sui functions receive objects as parameters
public entry fun modify(obj: &mut MyObject, value: u64) { }

// Aptos entry functions use &signer
public entry fun create_resource(account: &signer) { }

// Aptos acquires annotation for global storage access
public fun get_data(addr: address): &MyData acquires MyData {
    borrow_global<MyData>(addr)
}
```

---

## 5. Global Storage Operations

### Aptos Global Storage

```move
// Store a resource under an account
move_to<T>(account, resource);

// Check if a resource exists
exists<T>(address);

// Read a resource (immutable)
borrow_global<T>(address): &T

// Read a resource (mutable)
borrow_global_mut<T>(address): &mut T

// Remove a resource
move_from<T>(address): T
```

### Sui Object Storage

```move
// Create a new object
let obj = MyObject { id: object::new(ctx), field: value };

// Transfer to an address (owned)
transfer::public_transfer(obj, recipient);

// Share with everyone
transfer::public_share_object(obj);

// Freeze (immutable)
transfer::freeze_object(obj);

// Dynamic fields
dynamic_field::add(&mut parent.id, name, value);
dynamic_field::borrow(&parent.id, name): &T
dynamic_field::remove(&mut parent.id, name): T
```

---

## 6. Generics

Move supports generics with ability constraints:

```move
// Generic with ability constraint
public fun swap<T: key + store>(a: T, b: T): (T, T) {
    (b, a)
}

// Phantom type parameter
public struct Coin<phantom T> has key, store {
    id: UID,
    value: u64,
}

// Generic struct with store constraint
public struct Box<T: store> has key {
    id: UID,
    contents: T,
}
```

### Generic Type Safety

```move
// DANGEROUS: No constraints — can store anything
public struct UnsafeBox<T> has key {
    id: UID,
    value: T,
}

// SAFE: Proper constraints
public struct SafeBox<T: store> has key {
    id: UID,
    value: T,
}
```

---

## 7. Control Flow

```move
// If-else
if (condition) {
    // branch
} else {
    // branch
};

// While loop
let i = 0;
while (i < 10) {
    i = i + 1;
};

// Loop with break
let sum = 0;
loop {
    if (sum > 100) { break };
    sum = sum + 1;
};

// Match (Move 2024 edition)
match (value) {
    0 => handle_zero(),
    _ => handle_other(),
};
```

---

## 8. Constants and Error Codes

```move
// Constants
const MAX_SUPPLY: u64 = 1_000_000_000;
const DECIMALS: u8 = 9;

// Error constants (used with assert!)
const ENotAuthorized: u64 = 0;
const EInsufficientBalance: u64 = 1;
const EOverflow: u64 = 2;

// Usage
assert!(balance >= amount, EInsufficientBalance);
```

---

## 9. Common Patterns

### Capability Pattern (Sui)

```move
public struct AdminCap has key { id: UID }
public struct Config has key { id: UID, fee_rate: u64 }

public entry fun set_fee(
    _: &AdminCap,        // Must own capability
    config: &mut Config,
    new_rate: u64,
    _ctx: &mut TxContext
) {
    config.fee_rate = new_rate;
}
```

### Signer Validation Pattern (Aptos)

```move
public entry fun admin_action(admin: &signer) acquires Config {
    let addr = signer::address_of(admin);
    let config = borrow_global<Config>(@module_addr);
    assert!(addr == config.admin, ENotAuthorized);
    // ... privileged operation
}
```

### Witness / One-Time Witness Pattern

```move
// OTW: struct name matches module name (uppercase), only has drop
public struct MY_MODULE has drop {}

fun init(otw: MY_MODULE, ctx: &mut TxContext) {
    // OTW can only be created by the Move VM at module publish
    // This ensures init runs exactly once
}
```

### Publisher Pattern (Sui)

```move
fun init(otw: MY_MODULE, ctx: &mut TxContext) {
    // Publisher proves module ownership
    let publisher = publisher::claim(otw, ctx);
    transfer::public_share_object(publisher);
}
```

---

## 10. Testing

```move
#[test_only]
module my_package::my_module_tests {
    use my_package::my_module;

    #[test]
    fun test_basic() {
        // Test code here
    }

    #[test]
    #[expected_failure(abort_code = my_module::ENotAuthorized)]
    fun test_unauthorized() {
        // Should fail with specific error code
    }

    #[test(account = @0x1)]
    fun test_with_signer(account: &signer) {
        // Test with signer
    }
}
```

---

## 11. Primitive Types

| Type | Description | Range |
|------|-------------|-------|
| `bool` | Boolean | `true` / `false` |
| `u8` | 8-bit unsigned | 0 — 255 |
| `u16` | 16-bit unsigned | 0 — 65,535 |
| `u32` | 32-bit unsigned | 0 — 4,294,967,295 |
| `u64` | 64-bit unsigned | 0 — 18,446,744,073,709,551,615 |
| `u128` | 128-bit unsigned | 0 — 2^128-1 |
| `u256` | 256-bit unsigned | 0 — 2^256-1 |
| `address` | Account address | 32 bytes (Sui/Aptos) |
| `vector<T>` | Dynamic array | Variable length |
| `String` | UTF-8 string | Variable length |
| `Option<T>` | Optional value | `some(val)` / `none()` |

### Arithmetic Behavior

- All unsigned types wrap on overflow in release mode
- In debug/test mode, overflow causes abort
- Always use `assert!` for bounds checking in production code
- Move **does not** have built-in checked arithmetic like Solidity's `SafeMath`

```move
// Safe: explicit check
assert!(a <= MAX - b, EOverflow);
let result = a + b;

// Unsafe: wraps silently in release
let result = a + b; // May overflow!
```

## references/common/move-vulnerabilities.md

# Common Move Vulnerabilities

This document details Move language vulnerabilities that apply across **all** Move-based blockchains (Sui, Aptos, and others). For platform-specific vulnerabilities, see the Sui and Aptos resource files.

---

## M1. Improper Resource Abilities (CRITICAL)

### Description
Move's ability system (`copy`, `drop`, `key`, `store`) controls struct behavior at the compiler level. Incorrect abilities on asset/value types can lead to duplication or permanent loss of funds.

### Detection
```bash
# Find structs with asset-like names and copy/drop abilities
rg "public struct.*(Coin|Token|Asset|Balance|Vault|Share).*has.*(copy|drop)" sources/
# Find all structs with copy+drop on same type
rg "public struct.*has.*(copy.*drop|drop.*copy)" sources/
```

### Vulnerable Code
```move
// CRITICAL: Coin can be duplicated (copy)
public struct Coin has key, store, copy {
    value: u64,
}

// CRITICAL: Coin can be silently dropped/lost (drop)
public struct Coin has key, store, drop {
    value: u64,
}

// CRITICAL: Both copy and drop
public struct Token has key, store, copy, drop {
    amount: u64,
}
```

### Attack Scenarios
1. **Copy attack**: Attacker creates a coin, copies it, spends both copies — doubles their money
2. **Drop attack**: User receives coins, but they are accidentally dropped instead of being stored — permanent fund loss
3. **Both**: Unlimited money creation and silent destruction

### Secure Code
```move
// GOOD: Asset without copy or drop — must be explicitly handled
public struct Coin has key, store {
    value: u64,
}

// For burning, create explicit function
public entry fun burn(coin: Coin, _ctx: &mut TxContext) {
    let Coin { id, value: _ } = coin;
    object::delete(id);
}
```

---

## M2. Missing Access Control (CRITICAL)

### Description
Public or entry functions without proper authorization checks (capability, signer validation) allow anyone to execute privileged operations.

### Detection
```bash
# Find public/entry functions without capability or signer parameters
rg "public entry fun|public fun" sources/ | grep -v "Cap\b\|signer\|_ctx\|TxContext"
```

### Vulnerable Code
```move
// BAD: Anyone can call this privileged function
public entry fun set_fee_rate(
    config: &mut Config,
    new_rate: u64,
    _ctx: &mut TxContext
) {
    config.fee_rate = new_rate;
}

// BAD: Anyone can mint
public entry fun mint(
    treasury: &mut Treasury,
    amount: u64,
    recipient: address,
    ctx: &mut TxContext
) {
    transfer::public_transfer(
        treasury::withdraw(treasury, amount),
        recipient
    );
}
```

### Secure Code
```move
// GOOD: Requires admin capability
public entry fun set_fee_rate(
    _: &AdminCap,       // Capability gate
    config: &mut Config,
    new_rate: u64,
    _ctx: &mut TxContext
) {
    assert!(new_rate <= 10000, EInvalidRate);
    config.fee_rate = new_rate;
}

// GOOD: Requires minter capability
public entry fun mint(
    _: &MinterCap,      // Capability gate
    treasury: &mut Treasury,
    amount: u64,
    recipient: address,
    ctx: &mut TxContext
) {
    assert!(!treasury.paused, EPaused);
    assert!(amount <= treasury.max_mint, EExceedsLimit);
    transfer::public_transfer(
        treasury::withdraw(treasury, amount),
        recipient
    );
}
```

---

## M3. Witness Pattern Abuse (CRITICAL)

### Description
The Witness pattern is used to prove type ownership. If a witness can be created outside the module's `init` function, or has wrong abilities, attackers can forge proofs to mint tokens or create unauthorized types.

### Detection
```bash
# Find witness types
rg "Witness|witness" sources/
rg "public struct.*has drop" sources/
```

### Vulnerable Code
```move
// BAD: Witness creatable outside module init
public struct Witness has drop {}

public entry fun create_token(_: Witness, ...) {
    // Anyone can create Witness {} and call this
}

// BAD: Witness has store ability (can be saved and reused)
public struct Witness has drop, store {}

// BAD: Witness has copy ability (can be duplicated)
public struct Witness has drop, copy {}
```

### Secure Code
```move
// GOOD: One-Time Witness (OTW)
// Name must match module name in UPPERCASE, only has drop
public struct MY_TOKEN has drop {}

// GOOD: Only available during module initialization
fun init(otw: MY_TOKEN, ctx: &mut TxContext) {
    // OTW is consumed here, cannot be recreated
    coin::create_currency(otw, ...);
}
```

---

## M4. Capability Leakage (HIGH)

### Description
Capabilities (AdminCap, MintRef, BurnRef, etc.) that are transferred to unauthorized parties or can be claimed without authorization.

### Detection
```bash
# Find capability creation and transfer
rg "transfer.*Cap" sources/
rg "public_transfer.*Cap" sources/
rg "AdminCap|MintCap|OwnerCap|MintRef|BurnRef" sources/
```

### Vulnerable Code
```move
// BAD: Anyone can claim admin capability
public entry fun claim_admin(recipient: address, ctx: &mut TxContext) {
    transfer::public_transfer(
        AdminCap { id: object::new(ctx) },
        recipient
    );
}

// BAD: Capability transferable without authorization
public entry fun transfer_cap(cap: AdminCap, new_owner: address) {
    transfer::public_transfer(cap, new_owner);
}
```

### Secure Code
```move
// GOOD: Only at init, goes to publisher
fun init(ctx: &mut TxContext) {
    transfer::public_transfer(
        AdminCap { id: object::new(ctx) },
        tx_context::sender(ctx)
    );
}

// GOOD: Require existing admin to transfer
public entry fun transfer_admin(
    _: &AdminCap,    // Must already own admin cap
    cap: AdminCap,
    new_admin: address,
    _ctx: &mut TxContext
) {
    transfer::public_transfer(cap, new_admin);
}
```

---

## M5. Global Storage Errors (HIGH)

### Description
Unchecked `borrow_global`, missing `exists` checks, or incorrect `acquires` annotations causing runtime aborts or unexpected behavior.

### Detection
```bash
rg "borrow_global|borrow_global_mut" sources/
rg "move_to|move_from" sources/
rg "acquires" sources/
```

### Vulnerable Code
```move
// BAD: No existence check before borrow
public fun get_balance(addr: address): &mut u64 {
    borrow_global_mut<Balance>(addr)  // Aborts if not exists
}

// BAD: Double move_to without exists check
public entry fun init(account: &signer) {
    move_to<Config>(account, Config { ... });  // Aborts on second call
}
```

### Secure Code
```move
// GOOD: Check existence first
public fun get_balance(addr: address): &mut u64 {
    assert!(exists<Balance>(addr), ENotInitialized);
    borrow_global_mut<Balance>(addr)
}

// GOOD: Idempotent initialization
public entry fun init(account: &signer) {
    if (!exists<Config>(signer::address_of(account))) {
        move_to<Config>(account, Config { ... });
    };
}
```

---

## M6. Arithmetic Issues (MEDIUM)

### Description
Overflow/underflow in calculations. Move uses wrapping arithmetic in release mode — overflow does not revert but wraps around silently.

### Detection
```bash
# Find arithmetic in financial contexts
rg "balance.*\+|balance.*\-|amount.*\*|value.*\/" sources/
```

### Vulnerable Code
```move
// BAD: No overflow check
let new_balance = balance + deposit;  // Wraps on overflow!

// BAD: No underflow check
let remaining = balance - withdrawal;  // Wraps if withdrawal > balance!
```

### Secure Code
```move
// GOOD: Explicit overflow check
assert!(balance <= MAX_U64 - deposit, EOverflow);
let new_balance = balance + deposit;

// GOOD: Explicit underflow check
assert!(balance >= withdrawal, EInsufficientBalance);
let remaining = balance - withdrawal;
```

---

## M7. Type Confusion / Generic Misuse (HIGH)

### Description
Improper generic constraints allowing unauthorized types to be used where only specific types should be allowed.

### Detection
```bash
rg "<T>" sources/
rg "phantom" sources/
rg "public fun.*<T" sources/
```

### Vulnerable Code
```move
// BAD: No constraints on T — can store capabilities
public struct Box<T> has key {
    id: UID,
    value: T,
}

public entry fun store_anything<T>(value: T, ctx: &mut TxContext) {
    transfer::public_transfer(
        Box { id: object::new(ctx), value },
        tx_context::sender(ctx)
    );
}
```

### Secure Code
```move
// GOOD: Proper constraints
public struct Box<T: store> has key {
    id: UID,
    value: T,
}

// GOOD: Restrict to specific traits
public entry fun store_value<T: store + drop>(value: T, ctx: &mut TxContext) {
    transfer::public_transfer(
        Box { id: object::new(ctx), value },
        tx_context::sender(ctx)
    );
}
```

---

## M8. Missing Event Emission (LOW)

### Description
Critical state changes (transfers, mints, burns, config updates) without event emission, preventing off-chain monitoring and auditing.

### Detection
```bash
rg "sui::event|aptos_std::event|event::emit" sources/
```

### Vulnerable Code
```move
// BAD: Critical transfer with no event
public entry fun transfer(
    _: &AdminCap,
    treasury: &mut Treasury,
    amount: u64,
    recipient: address,
    ctx: &mut TxContext
) {
    transfer::public_transfer(
        treasury::withdraw(treasury, amount),
        recipient
    );
    // No event emitted!
}
```

### Secure Code
```move
public struct TransferEvent has drop, copy {
    amount: u64,
    recipient: address,
}

public entry fun transfer(
    _: &AdminCap,
    treasury: &mut Treasury,
    amount: u64,
    recipient: address,
    ctx: &mut TxContext
) {
    event::emit(TransferEvent { amount, recipient });
    transfer::public_transfer(
        treasury::withdraw(treasury, amount),
        recipient
    );
}
```

---

## Vulnerability Classification Summary

| ID | Category | Severity | CV equivalent |
|----|----------|----------|---------------|
| M1 | Improper Abilities | CRITICAL | Integer Overflow, Access Control |
| M2 | Missing Access Control | CRITICAL | Missing Authorization |
| M3 | Witness Pattern Abuse | CRITICAL | Authentication Bypass |
| M4 | Capability Leakage | HIGH | Privilege Escalation |
| M5 | Global Storage Errors | HIGH | Unchecked Return Value |
| M6 | Arithmetic Issues | MEDIUM | Integer Overflow/Underflow |
| M7 | Type Confusion | HIGH | Type Confusion |
| M8 | Missing Events | LOW | Missing Logging |

---

## Safe Patterns Checklist

| Pattern | Check |
|---------|-------|
| Asset struct abilities | No `copy`, no `drop` on asset types |
| Access control | All privileged functions gated by capability or signer check |
| Witness types | Only `drop` ability, only creatable in `init` |
| Capability transfer | Require existing authorization |
| Global storage | `exists` check before `borrow_global` |
| Arithmetic | Explicit bounds checking before operations |
| Generic types | Proper ability constraints |
| Events | Emit for all critical state changes |

## references/sui

```

```

## references/sui/CORE_VULNERABILITIES.md

#  Core Move Vulnerabilities

This document details core Move language vulnerabilities that apply across all Move-based blockchains.

---

## 1. Improper Resource Abilities

### Description
Move's ability system (`copy`, `drop`, `key`, `store`) controls how structs behave. Incorrect abilities on asset types can lead to duplication or loss of funds.

### Severity
CRITICAL

### Detection Pattern

```bash
# Find structs representing assets/value
rg "public struct.*Coin|public struct.*Token|public struct.*Asset" sources/
rg "has.*(copy|drop)" sources/
```

### Vulnerable Code

```move
// BAD: Coin can be duplicated
public struct Coin has key, store, copy {
    value: u64,
}

// BAD: Coin can be silently dropped (lost)
public struct Coin has key, store, drop {
    value: u64,
}

// BAD: Both issues combined
public struct Token has key, store, copy, drop {
    amount: u64,
}
```

### Attack Scenario

1. **Copy Attack**: User creates a coin, copies it, and spends both copies
2. **Drop Attack**: User receives payment, but coins are accidentally dropped, losing value

### Secure Code

```move
// GOOD: Asset without copy or drop - must be explicitly handled
public struct Coin has key, store {
    value: u64,
}

// For burning, create explicit function
public entry fun burn(coin: Coin, _ctx: &mut TxContext) {
    let Coin { value: _ } = coin;
    // Coin is consumed, value is burned
}
```

### Testing

```move
#[test]
#[expected_failure]
fun test_cannot_copy_coin() {
    let coin = Coin { value: 100 };
    let copy = coin; // This should fail to compile if copy is not allowed
}
```

---

## 2. Missing Access Control

### Description
Public or entry functions without proper authorization checks allow unauthorized operations.

### Severity
CRITICAL

### Detection Pattern

```bash
# Find entry and public functions
rg "public entry fun|public fun" sources/

# Check for capability/signer parameters
rg "public entry fun.*\(" sources/ | grep -v "Cap\|signer"
```

### Vulnerable Code

```move
module vulnerable::admin {
    public struct AdminCap has key { id: UID }
    public struct Config has key {
        id: UID,
        fee_rate: u64,
        paused: bool,
    }

    // BAD: Anyone can change fee rate
    public entry fun set_fee_rate(
        config: &mut Config,
        new_rate: u64,
        _ctx: &mut TxContext
    ) {
        config.fee_rate = new_rate;
    }

    // BAD: Anyone can pause the contract
    public entry fun emergency_pause(
        config: &mut Config,
        _ctx: &mut TxContext
    ) {
        config.paused = true;
    }

    // BAD: Anyone can mint tokens
    public entry fun mint(
        treasury: &mut Treasury,
        amount: u64,
        recipient: address,
        ctx: &mut TxContext
    ) {
        transfer::public_transfer(
            Coin { id: object::new(ctx), value: amount },
            recipient
        );
    }
}
```

### Attack Scenario

1. Attacker identifies unprotected `set_fee_rate` function
2. Attacker sets fee rate to 0
3. Attacker uses protocol without fees
4. Protocol loses all fee revenue

### Secure Code

```move
module secure::admin {
    public struct AdminCap has key { id: UID }
    public struct Config has key {
        id: UID,
        fee_rate: u64,
        paused: bool,
    }

    // GOOD: Requires admin capability
    public entry fun set_fee_rate(
        _: &AdminCap,  // Capability check
        config: &mut Config,
        new_rate: u64,
        _ctx: &mut TxContext
    ) {
        assert!(new_rate <= 10000, EInvalidRate); // Max 100%
        config.fee_rate = new_rate;
    }

    // GOOD: Requires admin capability
    public entry fun emergency_pause(
        _: &AdminCap,
        config: &mut Config,
        _ctx: &mut TxContext
    ) {
        config.paused = true;
    }

    // GOOD: Requires minter capability
    public entry fun mint(
        _: &MinterCap,
        treasury: &mut Treasury,
        amount: u64,
        recipient: address,
        ctx: &mut TxContext
    ) {
        assert!(!treasury.paused, EPaused);
        assert!(amount <= treasury.max_mint, EExceedsLimit);
        transfer::public_transfer(
            treasury::withdraw(treasury, amount),
            recipient
        );
    }
}
```

### Testing

```move
#[test_only]
module secure::admin_tests {
    use secure::admin;

    #[test]
    #[expected_failure(abort_code = admin::ENotAuthorized)]
    fun test_unauthorized_fee_change() {
        // Create config but no capability
        let config = admin::create_test_config();
        admin::set_fee_rate(&mut config, 500); // Should fail
    }

    #[test]
    fun test_authorized_fee_change() {
        let (cap, config) = admin::create_test_setup();
        admin::set_fee_rate(&cap, &mut config, 500);
        assert!(config.fee_rate == 500, 0);
    }
}
```

---

## 3. Witness Pattern Abuse

### Description
Witness pattern used incorrectly, allowing unauthorized type creation or token minting.

### Severity
CRITICAL

### Detection Pattern

```bash
# Find witness-related patterns
rg "Witness|witness" sources/
rg "public struct.*has drop" sources/
rg "ensure!|assert!.*witness" sources/
```

### Vulnerable Code

```move
module vulnerable::token {
    // BAD: Witness can be created anywhere
    public struct Witness has drop {}

    public fun create_collection(_: Witness, ctx: &mut TxContext) {
        // Create collection
    }

    public entry fun create_token(
        _: Witness,
        name: String,
        ctx: &mut TxContext
    ) {
        // Anyone can create this witness and call the function
        let witness = Witness {};
        create_collection(witness, ctx);
    }
}

// BAD: Witness with wrong abilities
module vulnerable::token2 {
    // Witness should only have drop
    public struct Witness has drop, store, copy {}

    public fun mint(_: Witness, amount: u64, ctx: &mut TxContext): Coin {
        Coin { id: object::new(ctx), value: amount }
    }
}
```

### Attack Scenario

1. Attacker sees Witness type with `drop` only but can be created publicly
2. Attacker creates Witness instance
3. Attacker calls mint function with forged witness
4. Attacker mints unlimited tokens

### Secure Code

```move
module secure::token {
    // GOOD: One-time witness (OTW) - can only be created at module init
    public struct WITNESS has drop {}

    // Only called once during module publish
    fun init(witness: WITNESS, ctx: &mut TxContext) {
        // Create collection with witness
        create_collection(witness, ctx);
    }

    // GOOD: Witness is passed as parameter, not creatable by users
    public fun mint(
        _: &mut WITNESS,  // Cannot be created by users
        amount: u64,
        ctx: &mut TxContext
    ): Coin {
        Coin { id: object::new(ctx), value: amount }
    }

    // Alternative: Use Publisher capability from Sui framework
    public fun mint_with_publisher(
        _: &Publisher,
        amount: u64,
        ctx: &mut TxContext
    ): Coin {
        // Publisher proves module ownership
        Coin { id: object::new(ctx), value: amount }
    }
}
```

### Testing

```move
#[test_only]
module secure::token_tests {
    use secure::token;

    #[test]
    #[expected_failure]
    fun test_cannot_create_witness() {
        // This should fail to compile - cannot create WITNESS outside module
        let witness = token::WITNESS {};
    }
}
```

---

## 4. Capability Leakage

### Description
Capabilities (admin, minter, etc.) transferred to unauthorized parties.

### Severity
HIGH

### Detection Pattern

```bash
# Find capability transfers
rg "transfer.*Cap" sources/
rg "public_transfer.*Cap" sources/

# Find capability creation
rg "AdminCap|MinterCap|OwnerCap" sources/
```

### Vulnerable Code

```move
module vulnerable::caps {
    public struct AdminCap has key { id: UID }

    // BAD: Anyone can claim admin capability
    public entry fun claim_admin_cap(
        recipient: address,
        ctx: &mut TxContext
    ) {
        transfer::public_transfer(
            AdminCap { id: object::new(ctx) },
            recipient
        );
    }

    // BAD: Capability can be redirected by any holder
    public entry fun transfer_admin_cap(
        cap: AdminCap,
        new_owner: address,
        ctx: &mut TxContext
    ) {
        transfer::public_transfer(cap, new_owner);
    }
}
```

### Attack Scenario

1. Attacker calls `claim_admin_cap` with their address
2. Attacker now has admin privileges
3. Attacker drains protocol funds or modifies critical parameters

### Secure Code

```move
module secure::caps {
    public struct AdminCap has key { id: UID }
    public struct CapState has key {
        id: UID,
        admin: address,
    }

    // GOOD: Only at module init, admin cap goes to publisher
    fun init(ctx: &mut TxContext) {
        transfer::public_transfer(
            AdminCap { id: object::new(ctx) },
            tx_context::sender(ctx)
        );
    }

    // GOOD: Require existing admin to transfer
    public entry fun transfer_admin_cap(
        _: &AdminCap,  // Must already have admin cap
        cap: AdminCap,
        new_admin: address,
        _ctx: &mut TxContext
    ) {
        transfer::public_transfer(cap, new_admin);
    }

    // GOOD: Multi-sig or timelock for sensitive operations
    public entry fun transfer_admin_with_delay(
        _: &AdminCap,
        _: &Timelock,
        cap: AdminCap,
        new_admin: address,
        _ctx: &mut TxContext
    ) {
        transfer::public_transfer(cap, new_admin);
    }
}
```

---

## 5. Improper Global Storage Access

### Description
Unchecked `borrow_global`, `borrow_global_mut`, or missing `acquires` leading to runtime errors or unexpected behavior.

### Severity
HIGH

### Detection Pattern

```bash
# Find global storage operations
rg "borrow_global|move_to|move_from|exists" sources/
rg "acquires" sources/
```

### Vulnerable Code

```move
module vulnerable::storage {
    public struct Balance has key { value: u64 }

    // BAD: No check if balance exists
    public entry fun withdraw(account: &mut signer, amount: u64): Balance {
        let addr = signer::address_of(account);
        // Will abort if Balance doesn't exist
        let balance = borrow_global_mut<Balance>(addr);
        assert!(balance.value >= amount, EInsufficientBalance);
        balance.value = balance.value - amount;
        Balance { value: amount }
    }

    // BAD: Race condition potential
    public entry fun transfer(from: &mut signer, to: address, amount: u64) {
        let addr = signer::address_of(from);
        let balance = borrow_global_mut<Balance>(addr);

        // Between this and the next borrow_global_mut, state could change
        // in concurrent transactions

        let dest = borrow_global_mut<Balance>(to);
        balance.value = balance.value - amount;
        dest.value = dest.value + amount;
    }
}
```

### Secure Code

```move
module secure::storage {
    public struct Balance has key { value: u64 }

    // GOOD: Check existence first
    public entry fun withdraw(account: &mut signer, amount: u64): Balance {
        let addr = signer::address_of(account);
        assert!(exists<Balance>(addr), EBalanceNotFound);

        let balance = borrow_global_mut<Balance>(addr);
        assert!(balance.value >= amount, EInsufficientBalance);
        balance.value = balance.value - amount;

        Balance { value: amount }
    }

    // GOOD: Atomic transfer with proper checks
    public entry fun transfer(from: &mut signer, to: address, amount: u64) acquires Balance {
        let addr = signer::address_of(from);

        // Check both exist
        assert!(exists<Balance>(addr), EBalanceNotFound);
        assert!(exists<Balance>(to), EDestNotFound);

        // Atomic borrow and modify
        let (src_balance, dest_balance) = (
            borrow_global_mut<Balance>(addr),
            borrow_global_mut<Balance>(to)
        );

        assert!(src_balance.value >= amount, EInsufficientBalance);

        src_balance.value = src_balance.value - amount;
        dest_balance.value = dest_balance.value + amount;
    }

    // GOOD: Initialize balance if not exists
    public entry fun deposit(account: &mut signer, balance: Balance) acquires Balance {
        let addr = signer::address_of(account);

        if (!exists<Balance>(addr)) {
            move_to(account, Balance { value: 0 });
        };

        let global = borrow_global_mut<Balance>(addr);
        let Balance { value } = balance;
        global.value = global.value + value;
    }
}
```

---

## 6. Arithmetic Issues

### Description
Overflow/underflow in calculations, especially in financial operations.

### Severity
MEDIUM

### Detection Pattern

```bash
# Find arithmetic operations
rg "\+|\-|\*" sources/
rg "checked_|saturating_" sources/
```

### Vulnerable Code

```move
module vulnerable::math {
    // BAD: Unchecked arithmetic
    public entry fun add_balance(
        balance: &mut Balance,
        amount: u64
    ) {
        balance.value = balance.value + amount; // Can overflow
    }

    // BAD: Subtraction without check
    public entry fun subtract(
        balance: &mut Balance,
        amount: u64
    ) {
        balance.value = balance.value - amount; // Can underflow
    }
}
```

### Secure Code

```move
module secure::math {
    // GOOD: Use checked arithmetic
    public entry fun add_balance(
        balance: &mut Balance,
        amount: u64
    ) {
        let new_value = balance.value + amount;
        assert!(new_value >= balance.value, EOverflow); // Overflow check
        balance.value = new_value;
    }

    // GOOD: Explicit bounds checking
    public entry fun subtract(
        balance: &mut Balance,
        amount: u64
    ) {
        assert!(balance.value >= amount, EUnderflow);
        balance.value = balance.value - amount;
    }

    // GOOD: Use safe math library
    public entry fun safe_add(
        balance: &mut Balance,
        amount: u64
    ) {
        balance.value = safe_math::add(balance.value, amount);
    }
}
```

---

## 7. Type Confusion

### Description
Improper use of generics or type casting leading to type confusion vulnerabilities.

### Severity
HIGH

### Detection Pattern

```bash
# Find generic usage
rg "T:|phantom|drop.*T" sources/
```

### Vulnerable Code

```move
module vulnerable::generics {
    // BAD: Improper generic constraints
    public struct Box<T> has key, store {
        value: T,
    }

    // Can store any type, including capabilities
    public entry fun store<T>(value: T, ctx: &mut TxContext) {
        transfer::public_transfer(
            Box { value },
            tx_context::sender(ctx)
        );
    }
}
```

### Secure Code

```move
module secure::generics {
    // GOOD: Proper constraints on generic types
    public struct Box<T: store> has key {
        id: UID,
        value: T,
    }

    // GOOD: Restrict what can be stored
    public entry fun store<T: store + drop>(
        value: T,
        ctx: &mut TxContext
    ) {
        transfer::public_transfer(
            Box { id: object::new(ctx), value },
            tx_context::sender(ctx)
        );
    }

    // GOOD: Use phantom for type markers without storing
    public struct Coin<phantom T> has key, store {
        id: UID,
        value: u64,
    }
}
```

---

## 8. Event Emission Issues

### Description
Missing, incorrect, or misleading event emissions that affect off-chain monitoring and auditing.

### Severity
LOW

### Detection Pattern

```bash
# Find event emissions
rg "sui::event|aptos::event|emit_event" sources/
```

### Vulnerable Code

```move
module vulnerable::events {
    // BAD: No events emitted for critical operations
    public entry fun transfer(
        _: &AdminCap,
        treasury: &mut Treasury,
        amount: u64,
        recipient: address,
        ctx: &mut TxContext
    ) {
        // Critical transfer with no event
        transfer::public_transfer(
            treasury::withdraw(treasury, amount),
            recipient
        );
    }
}
```

### Secure Code

```move
module secure::events {
    use sui::event;

    public struct TransferEvent has drop, copy {
        from: address,
        to: address,
        amount: u64,
        timestamp: u64,
    }

    // GOOD: Emit events for all critical operations
    public entry fun transfer(
        _: &AdminCap,
        treasury: &mut Treasury,
        amount: u64,
        recipient: address,
        ctx: &mut TxContext
    ) {
        let sender = tx_context::sender(ctx);

        event::emit(TransferEvent {
            from: sender,
            to: recipient,
            amount,
            timestamp: tx_context::timestamp(ctx),
        });

        transfer::public_transfer(
            treasury::withdraw(treasury, amount),
            recipient
        );
    }
}
```

---

## Summary Checklist

| Category | Check |
|----------|-------|
| Resource Abilities | Assets lack `copy` and `drop` |
| Access Control | All sensitive functions require capability |
| Witness Pattern | Witness types have only `drop` |
| Capability Leakage | Capabilities require existing auth to transfer |
| Global Storage | Check `exists` before `borrow_global` |
| Arithmetic | Use checked arithmetic for financial ops |
| Type Safety | Proper generic constraints |
| Events | Emit events for critical operations |

## references/sui/SUI_VULNERABILITIES.md

# Sui-Specific Vulnerabilities

This document details vulnerabilities specific to the Sui blockchain and its unique features.

---

## S1. Object Ownership Bypass

### Description
Sui's object model allows flexible ownership, but improper access control can allow unauthorized object manipulation or transfer.

### Severity
CRITICAL

### Detection Pattern

```bash
# Find object definitions and transfers
rg "public struct.*has key" sources/
rg "sui::transfer::public_transfer|transfer::transfer" sources/
rg "sui::object::new" sources/
```

### Vulnerable Code

```move
module vulnerable::vault {
    public struct Vault has key {
        id: UID,
        balance: Balance<CoinType>,
        owner: address,
    }

    // BAD: Anyone can transfer any vault
    public entry fun transfer_vault(
        vault: Vault,
        new_owner: address,
        _ctx: &mut TxContext
    ) {
        transfer::public_transfer(vault, new_owner);
    }

    // BAD: Owner check in wrong place
    public entry fun withdraw(
        vault: &mut Vault,
        amount: u64,
        ctx: &mut TxContext
    ): Coin<CoinType> {
        // No check if caller is owner!
        balance::withdraw(&mut vault.balance, amount)
    }
}
```

### Attack Scenario

1. Attacker identifies `transfer_vault` function
2. Attacker calls function with victim's vault object
3. Vault is transferred to attacker
4. Attacker drains all funds

### Secure Code

```move
module secure::vault {
    public struct Vault has key {
        id: UID,
        balance: Balance<CoinType>,
    }

    public struct VaultOwnerCap has key { id: UID, vault_id: ID }

    // GOOD: Only owner with capability can transfer
    public entry fun transfer_vault(
        _: &VaultOwnerCap,
        vault: Vault,
        new_owner: address,
        ctx: &mut TxContext
    ) {
        transfer::public_transfer(vault, new_owner);
    }

    // GOOD: Capability-based access control
    public entry fun withdraw(
        _: &VaultOwnerCap,
        vault: &mut Vault,
        amount: u64,
        ctx: &mut TxContext
    ): Coin<CoinType> {
        balance::withdraw(&mut vault.balance, amount)
    }

    // Initialize with capability pattern
    fun init(ctx: &mut TxContext) {
        let sender = tx_context::sender(ctx);
        let vault = Vault {
            id: object::new(ctx),
            balance: balance::zero(),
        };
        let vault_id = object::id(&vault);

        transfer::public_transfer(vault, sender);
        transfer::public_transfer(
            VaultOwnerCap { id: object::new(ctx), vault_id },
            sender
        );
    }
}
```

---

## S2. Shared Object Manipulation

### Description
Shared objects in Sui can be accessed concurrently, leading to race conditions and unexpected state changes.

### Severity
CRITICAL

### Detection Pattern

```bash
# Find shared objects
rg "public_share_object|shared_object" sources/
rg "sui::transfer::share_object" sources/
```

### Vulnerable Code

```move
module vulnerable::marketplace {
    public struct Listing has key {
        id: UID,
        price: u64,
        seller: address,
        item: Option<Item>,
    }

    // BAD: Race condition on shared listing
    public entry fun buy(
        listing: &mut Listing,
        payment: Coin<SUI>,
        buyer: address,
        ctx: &mut TxContext
    ) {
        let price = listing.price;
        assert!(coin::value(&payment) >= price, EInsufficientPayment);

        // Race condition: Multiple buyers could reach here simultaneously
        let item = option::extract(&mut listing.item);

        // Payment and transfer happen after item extraction
        // But another transaction might have already taken the item

        transfer::public_transfer(item, buyer);
        transfer::public_transfer(payment, listing.seller);
    }
}
```

### Attack Scenario

1. Item listed for 100 SUI
2. Buyer A starts transaction, passes price check
3. Buyer B starts transaction, passes price check
4. Both transactions extract the item
5. One gets item, one gets nothing but still pays

### Secure Code

```move
module secure::marketplace {
    public struct Listing has key {
        id: UID,
        price: u64,
        seller: address,
        item: Option<Item>,
        sold: bool,  // GOOD: Track sale status
    }

    public entry fun buy(
        listing: &mut Listing,
        payment: Coin<SUI>,
        buyer: address,
        ctx: &mut TxContext
    ) {
        assert!(!listing.sold, EAlreadySold);
        assert!(option::is_some(&listing.item), EAlreadySold);

        let price = listing.price;
        assert!(coin::value(&payment) >= price, EInsufficientPayment);

        // GOOD: Mark as sold before extraction
        listing.sold = true;

        let item = option::extract(&mut listing.item);

        transfer::public_transfer(item, buyer);
        transfer::public_transfer(payment, listing.seller);
    }

    // BETTER: Use Kiosk for atomic trades
    // See S3 for Kiosk pattern
}
```

---

## S3. Kiosk Exploitation

### Description
Sui Kiosk provides a secure trading mechanism, but improper implementation can bypass its protections.

### Severity
HIGH

### Detection Pattern

```bash
# Find kiosk usage
rg "sui::kiosk" sources/
rg "kiosk::purchase|kiosk::list" sources/
```

### Vulnerable Code

```move
module vulnerable::kiosk_trade {
    use sui::kiosk::{Self, Kiosk};

    // BAD: Missing purchase validation
    public entry fun purchase_from_kiosk(
        kiosk: &mut Kiosk,
        item_id: ID,
        payment: Coin<SUI>,
        ctx: &mut TxContext
    ) {
        // No check if item is actually listed for this price
        let item = kiosk::purchase(kiosk, item_id, payment);
        transfer::public_transfer(item, tx_context::sender(ctx));
    }

    // BAD: Bypassing kiosk entirely
    public entry fun direct_transfer(
        item: Item,
        recipient: address,
        _ctx: &mut TxContext
    ) {
        // Item should only be transferred through kiosk
        transfer::public_transfer(item, recipient);
    }
}
```

### Secure Code

```move
module secure::kiosk_trade {
    use sui::kiosk::{Self, Kiosk, KioskOwnerCap};

    public struct Item has key, store { id: UID, rarity: u8 }
    public struct ItemPolicy has key { id: UID }

    // GOOD: Proper kiosk purchase with policy
    public entry fun purchase_from_kiosk(
        kiosk: &mut Kiosk,
        item_id: ID,
        payment: Coin<SUI>,
        ctx: &mut TxContext
    ) {
        let (item, receipt) = kiosk::purchase(kiosk, item_id, payment);

        // Validate purchase through policy
        let policy = object::borrow_global<ItemPolicy>(
            tx_context::sender(ctx)
        );
        validate_purchase(policy, &item);

        kiosk::finalize_purchase(kiosk, receipt, sui::kiosk::prove_purchase());
        transfer::public_transfer(item, tx_context::sender(ctx));
    }

    // GOOD: Item can only be placed in kiosk
    fun init(ctx: &mut TxContext) {
        let (kiosk, kiosk_owner_cap) = kiosk::new(ctx);
        let policy = ItemPolicy { id: object::new(ctx) };

        transfer::public_transfer(kiosk, tx_context::sender(ctx));
        transfer::public_transfer(kiosk_owner_cap, tx_context::sender(ctx));
        transfer::share_object(policy);
    }
}
```

---

## S4. PTB (Programmable Transaction Block) Composition Attacks

### Description
PTBs allow composing multiple operations, which can be exploited to create unintended transaction flows.

### Severity
HIGH

### Detection Pattern

```bash
# Find functions that could be composed maliciously
rg "public entry fun" sources/
rg "public fun.*returns" sources/
```

### Vulnerable Code

```move
module vulnerable::lending {
    public struct Position has key {
        id: UID,
        collateral: Coin<SUI>,
        borrowed: Balance<USDC>,
    }

    // BAD: Separate functions can be composed maliciously
    public entry fun deposit_collateral(
        position: &mut Position,
        collateral: Coin<SUI>
    ) {
        position.collateral = coin::into_balance(collateral);
    }

    public entry fun borrow(
        position: &mut Position,
        amount: u64,
        ctx: &mut TxContext
    ): Coin<USDC> {
        // In PTB, can borrow immediately after deposit
        // without waiting for price confirmation
        let max_borrow = coin::value(&position.collateral) / 2;
        assert!(amount <= max_borrow, EOverBorrow);

        balance::withdraw(&mut position.borrowed, amount)
    }

    public entry fun withdraw_collateral(
        position: &mut Position,
        ctx: &mut TxContext
    ): Coin<SUI> {
        // In PTB, can withdraw right after borrow
        let collateral = position.collateral;
        position.collateral = coin::zero();
        coin::from_balance(collateral, ctx)
    }
}
```

### Attack Scenario via PTB:

1. `deposit_collateral(1000 SUI)`
2. `borrow(500 USDC)` (based on 1000 SUI collateral)
3. `withdraw_collateral()` (no collateral left!)
4. Protocol left with bad debt

### Secure Code

```move
module secure::lending {
    public struct Position has key {
        id: UID,
        collateral: Balance<SUI>,
        borrowed: Balance<USDC>,
        last_action_epoch: u64,
    }

    // GOOD: Check health ratio before any withdrawal
    public entry fun withdraw_collateral(
        position: &mut Position,
        amount: u64,
        ctx: &mut TxContext
    ): Coin<SUI> {
        let collateral_value = balance::value(&position.collateral);
        let borrowed_value = balance::value(&position.borrowed);

        assert!(collateral_value >= amount, EInsufficientCollateral);

        // Calculate health factor after withdrawal
        let new_collateral = collateral_value - amount;
        let health_factor = (new_collateral * 100) / borrowed_value;

        assert!(health_factor >= 150, EUnhealthyPosition); // 150% minimum

        balance::withdraw(&mut position.collateral, amount)
    }

    // GOOD: Use flash loan pattern for atomic operations
    public entry fun flash_loan(
        position: &mut Position,
        amount: u64,
        callback: &mut receiver::FlashLoanReceiver,
        ctx: &mut TxContext
    ) {
        let loan = balance::withdraw(&mut position.borrowed, amount);
        let loan_value = balance::value(&loan);

        // Callback must repay within same transaction
        receiver::receive(callback, loan, ctx);

        // Verify repayment
        assert!(
            balance::value(&position.borrowed) >= loan_value,
            EFlashLoanNotRepaid
        );
    }
}
```

---

## S5. Dynamic Field Abuse

### Description
Dynamic fields in Sui allow attaching data to objects, but improper access control can lead to unauthorized modifications.

### Severity
HIGH

### Detection Pattern

```bash
# Find dynamic field usage
rg "sui::dynamic_field|dynamic_object_field" sources/
rg "add|remove|borrow" sources/ | grep "dynamic"
```

### Vulnerable Code

```move
module vulnerable::metadata {
    use sui::dynamic_field;

    public struct NFT has key, store { id: UID }
    public struct Metadata has store { rarity: u8, power: u64 }

    // BAD: Anyone can modify metadata
    public entry fun set_rarity(
        nft: &mut NFT,
        rarity: u8,
        _ctx: &mut TxContext
    ) {
        dynamic_field::add(&mut nft.id, b"rarity", rarity);
    }

    // BAD: No validation on metadata
    public entry fun update_power(
        nft: &mut NFT,
        power: u64
    ) {
        if (dynamic_field::exists_(&nft.id, b"power")) {
            dynamic_field::remove(&mut nft.id, b"power");
        };
        dynamic_field::add(&mut nft.id, b"power", power);
    }
}
```

### Secure Code

```move
module secure::metadata {
    use sui::dynamic_field;

    public struct NFT has key, store { id: UID }
    public struct Metadata has store { rarity: u8, power: u64 }
    public struct AdminCap has key { id: UID }

    // GOOD: Only admin can modify metadata
    public entry fun set_rarity(
        _: &AdminCap,
        nft: &mut NFT,
        rarity: u8,
        _ctx: &mut TxContext
    ) {
        assert!(rarity <= 5, EInvalidRarity);

        if (dynamic_field::exists_(&nft.id, b"metadata")) {
            dynamic_field::remove<Metadata>(&mut nft.id);
        };
        dynamic_field::add(&mut nft.id, b"metadata", Metadata {
            rarity,
            power: calculate_power(rarity),
        });
    }

    // GOOD: Power derived from rarity, not settable
    fun calculate_power(rarity: u8): u64 {
        (rarity as u64) * 100
    }
}
```

---

## S6. Transfer Policy Bypass

### Description
Sui's transfer policies enforce rules on asset transfers, but improper implementation can allow bypassing these rules.

### Severity
HIGH

### Detection Pattern

```bash
# Find transfer policy usage
rg "sui::transfer_policy|TransferPolicy" sources/
rg "TransferRequest|prove" sources/
```

### Vulnerable Code

```move
module vulnerable::regulated_coin {
    public struct REGULATED_COIN has drop {}
    public struct RegulatedCoin has key, store { id: UID, value: u64 }
    public struct TransferPolicy has key { id: UID }

    // BAD: No transfer policy enforcement
    public entry fun transfer(
        coin: RegulatedCoin,
        recipient: address,
        _ctx: &mut TxContext
    ) {
        // Bypasses any transfer rules
        transfer::public_transfer(coin, recipient);
    }

    // BAD: Policy check is optional
    public entry fun checked_transfer(
        coin: RegulatedCoin,
        recipient: address,
        policy: Option<&TransferPolicy>,
        ctx: &mut TxContext
    ) {
        if (option::is_some(policy)) {
            // Policy check optional
            validate_transfer(option::borrow(policy), &coin);
        };
        transfer::public_transfer(coin, recipient);
    }
}
```

### Secure Code

```move
module secure::regulated_coin {
    use sui::transfer_policy::{Self, TransferPolicy, TransferRequest};

    public struct REGULATED_COIN has drop {}
    public struct RegulatedCoin has key, store { id: UID, value: u64 }

    // GOOD: Enforce transfer policy
    public entry fun transfer(
        coin: RegulatedCoin,
        recipient: address,
        policy: &TransferPolicy<REGULATED_COIN>,
        ctx: &mut TxContext
    ) {
        let (coin, request) = transfer_policy::request(coin, policy, ctx);

        // Enforce KYC/AML rules
        assert!(is_kyc_approved(recipient), ENotKYC);
        assert!(!is_sanctioned(recipient), ESanctioned);

        transfer_policy::approve(policy, request);
        transfer::public_transfer(coin, recipient);
    }

    // GOOD: Use kiosk for compliant transfers
    public entry fun kiosk_transfer(
        kiosk: &mut Kiosk,
        coin: RegulatedCoin,
        policy: &TransferPolicy<REGULATED_COIN>,
        ctx: &mut TxContext
    ) {
        let (coin, request) = transfer_policy::request(coin, policy, ctx);
        transfer_policy::confirm_request(policy, request);

        kiosk::deposit(kiosk, coin);
    }
}
```

---

## Sui Security Best Practices

### 1. Object Model Patterns

```move
// Pattern: Capability-based ownership
public struct OwnedItem has key { id: UID }
public struct OwnerCap has key { id: UID, item_id: ID }

// Pattern: Shared state with mutex-like access
public struct SharedState has key { id: UID, locked: bool }
public struct LockCap has key { id: UID }
```

### 2. Transfer Patterns

```move
// Always check ownership before transfer
public entry fun transfer_item(
    _: &OwnerCap,
    item: Item,
    recipient: address
) { ... }

// Use kiosk for marketplace operations
kiosk::list(kiosk, item_id, price);
```

### 3. Concurrency Safety

```move
// Use status flags for shared objects
public struct Listing has key {
    sold: bool,
    cancelled: bool,
}

// Check status before operations
assert!(!listing.sold && !listing.cancelled, EInvalidState);
```

### 4. PTB Safety

```move
// Always verify final state
public entry fun final_health_check(position: &Position) {
    let health = calculate_health(position);
    assert!(health >= MIN_HEALTH, EUnhealthyPosition);
}
```

---

## Summary Checklist

| Check | Description |
|-------|-------------|
| Object Ownership | Capability required for transfers |
| Shared Objects | Status flags for concurrent access |
| Kiosk | All trades through kiosk |
| PTB Safety | State verified at end of operations |
| Dynamic Fields | Access-controlled modifications |
| Transfer Policy | Required for regulated assets |

## references/sui/ability-analysis.md

---
name: "ability-analysis"
description: "Trigger Pattern Always (Sui Move) -- foundational security check - Inject Into Breadth agents, depth agents"
---

# ABILITY_ANALYSIS Skill

> **Trigger Pattern**: Always (Sui Move) -- foundational security check
> **Inject Into**: Breadth agents, depth agents

For every struct defined in the protocol:

**STEP PRIORITY**: Steps 5 (Hot Potato Enforcement) and 7 (Dynamic Field Ability Propagation) are where HIGH/CRITICAL severity findings most commonly hide. Do NOT rush these steps. If constrained, skip conditional sections before skipping 5 or 7.

## 1. Struct Ability Inventory

Enumerate ALL structs across all modules:

| Module | Struct | Abilities | Has `id: UID`? | Is Object? | Transferable? | Notes |
|--------|--------|-----------|----------------|------------|---------------|-------|
| {mod} | {name} | {key, store, drop, copy} | YES/NO | YES/NO | YES/NO | {context} |

**Sui ability semantics**:
- `key` = Object type. MUST have `id: UID` as the first field. Can be owned, shared, or frozen.
- `store` = Can be transferred freely via `public_transfer` / `public_share_object`. Can be stored inside other objects via dynamic fields or wrapping.
- `drop` = Can be implicitly discarded. Without `drop`, the value MUST be explicitly consumed (unpacked, transferred, or destroyed).
- `copy` = Can be duplicated. `copy + key` is IMPOSSIBLE in Sui -- objects cannot be copied.
- No abilities at all = Hot potato pattern. Value must be consumed within the same transaction.

**Consistency check**: For each struct with `key`:
- [ ] Does it have `id: UID` as the FIRST field? If not -> compilation error (catch misplaced UID).
- [ ] Is `store` intentionally included or omitted? `key` without `store` = only the defining module can transfer it (custom transfer rules).

## 2. Object Model Classification

Classify each object (`key` ability) by ownership model:

| Object | Ownership | Created Via | Transfer Restricted? | Freeze Possible? |
|--------|-----------|-------------|---------------------|-----------------|
| {name} | Owned / Shared / Frozen / Wrapped | {function} | YES (no `store`) / NO (`store`) | YES/NO |

**Security checks per ownership type**:

### 2a. Owned Objects
- Can the owner transfer to themselves via `transfer::transfer` to reset state?
- Are there time-locks or cooldowns that reset on transfer?
- Can owned objects be wrapped inside other objects to bypass module restrictions?

### 2b. Shared Objects
- Is the object made shared via `transfer::share_object` at creation?
- Once shared, can never be un-shared -- is this intended?
- Shared objects require consensus ordering -- are there ordering-dependent operations?
- **Critical**: Can an attacker create a competing shared object of the same type?

### 2c. Frozen Objects (Immutable)
- Is the object frozen via `transfer::freeze_object`?
- Once frozen, can never be mutated -- is this intended?
- Are there references to the frozen object that expect mutation?

### 2d. Wrapped Objects
- Objects stored as fields inside other objects lose their independent existence.
- Can wrapping bypass transfer restrictions (object with `key` only, no `store`, wrapped inside a `key + store` parent)?
- When unwrapped, does the object retain its original ID and state?

## 3. Ability Mismatch Analysis

For each struct, verify ability assignments match intended behavior:

### 3a. Missing `drop` -- Intentional?
| Struct | Has `drop`? | Explicit Destroy Function? | Can Leak? |
|--------|------------|---------------------------|-----------|
| {name} | NO | YES: `destroy_{name}()` / NO | YES/NO |

**Rule**: A struct without `drop` that has no explicit destroy/consume path creates a resource leak. The transaction will abort if the value is not consumed. This is sometimes intentional (hot potato) but often a bug when the struct is created in error paths.

### 3b. Unnecessary `store` -- Over-Permissive?
| Struct | Has `store`? | Stored in Dynamic Fields? | Freely Transferable? | Should Be Restricted? |
|--------|-------------|--------------------------|---------------------|----------------------|
| {name} | YES | YES/NO | YES | {analysis} |

**Check**: If a struct has `store` but the protocol intends restricted transfers (e.g., non-transferable receipts, bound tickets), the `store` ability enables bypass via `public_transfer`. Does any security invariant depend on transfer restriction?

### 3c. `copy` Abuse Potential
| Struct | Has `copy`? | Contains Balances/IDs? | Duplication Dangerous? |
|--------|------------|----------------------|----------------------|
| {name} | YES/NO | YES/NO | YES/NO |

**Rule**: `copy` on a struct containing `Balance<T>`, capability tokens, or unique identifiers is almost always a bug -- it enables double-spending or capability duplication. `copy + key` is impossible (enforced by Sui), but `copy + store` on inner structs is allowed and dangerous if they hold value.

## 4. Capability Pattern Audit

Identify all capability/admin structs:

| Capability | Abilities | Created In | Transferred To | Can Be Duplicated? | Revocable? |
|-----------|-----------|------------|---------------|-------------------|-----------|
| {name} | {abilities} | `init()` | {recipient} | YES (`copy`) / NO | YES/NO |

**Checks**:
- Is the capability created only in `init()` (module initializer)? If created elsewhere, can it be minted by unauthorized parties?
- Does the capability have `store`? If yes, the holder can transfer it freely -- is this intended?
- Is there a revocation mechanism? (Capability patterns in Sui are typically one-way -- once issued, not revocable without wrapping in a shared object with access control.)
- **One-Time Witness (OTW) vs Capability**: Is this struct actually an OTW being misused as a persistent capability? OTW types should be consumed in `init`, not stored.

## 5. Hot Potato Enforcement

Identify all structs with NO abilities:

| Struct | Module | Created By | Must Be Consumed By | Enforced? |
|--------|--------|------------|--------------------|---------:|
| {name} | {mod} | {function} | {function} | YES/NO |

**Hot potato security checks**:
- [ ] Is the hot potato created and consumed within a single PTB (Programmable Transaction Block)?
- [ ] Can the consumption function be called by anyone, or only specific callers?
- [ ] Does the consumption function validate the hot potato's contents match expectations?
- [ ] Can an attacker create a fake hot potato of the same type from a different module? (NO -- Move type system prevents cross-module struct creation.)
- [ ] Can the hot potato be stored if someone adds `store` via a wrapper? (Check: is there a public wrapper that accepts arbitrary `store` types.)
- [ ] **Transaction abort impact**: If the hot potato cannot be consumed (e.g., consumption function reverts), the entire PTB aborts. Can this be used for griefing? (e.g., attacker causes the consumption precondition to fail after the hot potato is created.)

**Pattern validation**: Trace every hot potato from creation to consumption. Document the full lifecycle:
```
create: module::start_action() -> HotPotato
  ... intervening calls that rely on HotPotato's existence ...
consume: module::finish_action(potato: HotPotato)
```
If any code path creates a hot potato without a guaranteed consumption path -> FINDING (transaction will always abort on that path).

## 6. Transfer Restriction Analysis

For objects with `key` but NOT `store`:

| Object | Module Transfer Function | Custom Rules | Bypass Possible? |
|--------|------------------------|-------------|-----------------|
| {name} | {function or NONE} | {description} | YES/NO |

**Sui transfer rules**:
- `key + store`: Anyone can transfer via `transfer::public_transfer`.
- `key` only: Only the defining module can transfer via `transfer::transfer` (requires module-level access).
- **Bypass check**: Can the restricted object be wrapped inside a `store`-capable struct, then the wrapper transferred freely? If the wrapping struct is from a DIFFERENT module, this is a transfer restriction bypass.

**Check each restricted object**:
1. Does any public function accept this object type and wrap it?
2. Does any public function accept this object type and place it in a dynamic field of a freely transferable object?
3. If yes to either -> the transfer restriction is bypassable -> FINDING.

## 7. Dynamic Field Ability Propagation

For every use of `dynamic_field::add` or `dynamic_object_field::add`:

| Parent Object | Field Key Type | Field Value Type | Value Has `store`? | Parent Has `store`? |
|--------------|---------------|-----------------|-------------------|-------------------|
| {parent} | {key_type} | {value_type} | YES/NO | YES/NO |

**Rules**:
- `dynamic_field::add` requires the value type to have `store`.
- `dynamic_object_field::add` requires the value type to have `key + store`.
- **Security check**: If a value with `store` is added as a dynamic field, anyone who can access the parent object can potentially extract it via `dynamic_field::remove`. Is extraction access-controlled?
- **Orphan check**: If the parent object is destroyed, are dynamic fields cleaned up? Orphaned dynamic fields remain in storage and can never be accessed again -> permanent storage leak.
- **Type confusion**: Dynamic fields are keyed by type. Can an attacker add a dynamic field with a key type that collides with an expected key type? (Unlikely due to Move type system, but check for generic key types like `vector<u8>` or `String`.)

## 8. Module Initializer Audit

For each module with an `init` function:

| Module | `init` Parameters | Objects Created | Capabilities Issued | OTW Consumed? |
|--------|------------------|-----------------|--------------------|--------------:|
| {mod} | {params} | {list} | {list} | YES/NO/N/A |

**Checks**:
- Is `init` the ONLY place critical capabilities are created?
- Does `init` properly consume the One-Time Witness if one is passed?
- Can the module be re-initialized via package upgrade? (Sui package upgrades do NOT re-run `init`.)
- Are shared objects created in `init`? (They must be -- you cannot share an owned object after creation in Sui.)

## Finding Template

```markdown
**ID**: [AB-N]
**Severity**: [based on ability misuse impact]
**Step Execution**: check1,2,3,4,5,6,7,8 | X(reasons) | ?(uncertain)
**Rules Applied**: [R4:Y, R5:Y, R10:Y, ...]
**Location**: module::struct_name
**Title**: [Ability issue type] in [struct] enables [attack/bypass]
**Description**: [Specific ability misconfiguration with type-level trace]
**Impact**: [What breaks: transfer restriction bypass, capability duplication, resource leak, hot potato griefing]
```

---

## Step Execution Checklist (MANDATORY)

> **CRITICAL**: You MUST report completion status for ALL sections. Findings with incomplete sections will be flagged for depth review.

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Struct Ability Inventory | YES | Y/X/? | |
| 2. Object Model Classification | YES | Y/X/? | |
| 2b. Shared Object Analysis | IF shared objects | Y/X(N/A)/? | |
| 3. Ability Mismatch Analysis | YES | Y/X/? | |
| 3b. Unnecessary `store` Check | YES | Y/X/? | |
| 3c. `copy` Abuse Check | YES | Y/X/? | |
| 4. Capability Pattern Audit | YES | Y/X/? | |
| 5. Hot Potato Enforcement | IF hot potatoes exist | Y/X(N/A)/? | **HIGH PRIORITY** |
| 6. Transfer Restriction Analysis | IF `key`-only objects | Y/X(N/A)/? | |
| 7. Dynamic Field Ability Propagation | IF dynamic fields used | Y/X(N/A)/? | **HIGH PRIORITY** |
| 8. Module Initializer Audit | YES | Y/X/? | |

### Cross-Reference Markers

**After Section 4** (Capability Pattern Audit):
- Cross-reference with `TYPE_SAFETY.md` Section on OTW analysis
- IF capability has `store` -> flag for SEMI_TRUSTED_ROLES analysis

**After Section 5** (Hot Potato Enforcement):
- IF hot potato consumption depends on external state -> cross-reference with EXTERNAL_PRECONDITION_AUDIT
- IF hot potato abort causes shared object locking -> document consensus impact

**After Section 7** (Dynamic Field Ability Propagation):
- IF dynamic field values extractable by non-owners -> FINDING (minimum Medium)
- Cross-reference with TOKEN_FLOW_TRACING for dynamic field token storage

## references/sui/attack-vectors.md

# Sui Move Attack Vectors Catalog

Known attack vectors for Sui Move smart contracts, organized by category. Each vector includes detection patterns.

---

## 1. Ability Exploitation

### 1.1 Asset Duplication via `copy`
- **Severity**: CRITICAL
- **Pattern**: Struct representing value has `copy` ability
- **Impact**: Unlimited fund duplication
- **Detection**: `rg "public struct.*(Coin|Token|Asset|Balance).*has.*copy" sources/`

### 1.2 Silent Loss via `drop`
- **Severity**: CRITICAL
- **Pattern**: Struct representing value has `drop` ability
- **Impact**: Funds silently discarded
- **Detection**: `rg "public struct.*(Coin|Token|Asset|Balance).*has.*drop" sources/`

### 1.3 Unauthorized Storage via `store`
- **Severity**: HIGH
- **Pattern**: Sensitive capability has `store`, can be embedded in attacker objects
- **Impact**: Capability extracted and used from wrapper
- **Detection**: `rg "public struct.*Cap.*has.*store" sources/`

---

## 2. Access Control Bypass

### 2.1 Ungated Entry Functions
- **Severity**: CRITICAL
- **Pattern**: `public entry fun` without capability check
- **Impact**: Unauthorized privileged operations
- **Detection**: `rg "public entry fun" sources/ | grep -v "Cap\|TxContext"`

### 2.2 Object Theft
- **Severity**: CRITICAL
- **Pattern**: `public_transfer` without ownership or capability check
- **Impact**: Anyone can transfer any object passed to them
- **Detection**: `rg "public_transfer" sources/`

### 2.3 Capability Claiming
- **Severity**: HIGH
- **Pattern**: Function creates and transfers capability without authorization
- **Impact**: Anyone gains admin/minter privileges
- **Detection**: `rg "public.*fun.*claim\|public.*fun.*create.*Cap.*transfer" sources/`

---

## 3. Witness and Type System Abuse

### 3.1 Forgeable Witness
- **Severity**: CRITICAL
- **Pattern**: Witness struct can be instantiated outside module `init`
- **Impact**: Unauthorized type creation, token minting
- **Detection**: `rg "public struct.*has drop" sources/` then verify struct is not OTW

### 3.2 Witness Reuse
- **Severity**: HIGH
- **Pattern**: Witness has `store` or `copy`, allowing it to be saved and reused
- **Impact**: Witness consumed in init but copy retained for later abuse
- **Detection**: `rg "public struct.*Witness.*has.*(store|copy)" sources/`

### 3.3 Generic Type Confusion
- **Severity**: HIGH
- **Pattern**: Generic functions without proper ability constraints
- **Impact**: Store/extract unauthorized types
- **Detection**: `rg "public fun.*<T>" sources/ | grep -v "phantom\|store\|key"`

---

## 4. Concurrency and State Issues

### 4.1 Shared Object Race
- **Severity**: CRITICAL
- **Pattern**: Shared object modified without status flag or consistency check
- **Impact**: Double-spending, inconsistent state
- **Detection**: `rg "share_object\|shared_object" sources/` then check for status tracking

### 4.2 PTB Composition Attack
- **Severity**: HIGH
- **Pattern**: Functions that should be atomic but can be composed maliciously in a PTB
- **Example**: Deposit → Borrow → Withdraw in same transaction without health check
- **Detection**: Look for separate deposit/borrow/withdraw functions that lack post-operation health verification

### 4.3 Stale Parameters
- **Severity**: MEDIUM
- **Pattern**: Parameters read from storage at beginning of multi-step operation, but state may change between steps
- **Impact**: Operations based on stale data
- **Detection**: Look for sequential `borrow_global` calls or multi-step operations on shared objects

---

## 5. Economic Attacks

### 5.1 Flash Loan Attack
- **Severity**: HIGH
- **Pattern**: Price/oracle manipulation within a single transaction using borrowed funds
- **Impact**: Draining liquidity pools, manipulating prices
- **Detection**: `rg "flash\|loan\|borrow.*deposit" sources/`

### 5.2 First Depositor / Zero-State Issue
- **Severity**: MEDIUM
- **Pattern**: Division by zero or rate manipulation when vault/pool has zero or near-zero deposits
- **Impact**: First depositor gets inflated share ratio
- **Detection**: `rg "shares.*total_supply\|balance.*\.value.*/" sources/`

### 5.3 Rounding Exploitation
- **Severity**: MEDIUM
- **Pattern**: Integer division truncation that can be exploited for small gains at scale
- **Impact**: Systematic value extraction
- **Detection**: `rg " \/ " sources/` in financial calculation contexts

### 5.4 Share Inflation Attack
- **Severity**: HIGH
- **Pattern**: Attacker deposits dust, donates inflated tokens, then redeems for disproportionate share
- **Impact**: Theft of other depositors' funds
- **Detection**: Look for vault deposit/withdraw without minimum deposit or offset

---

## 6. Storage and State Management

### 6.1 Unbounded Storage Growth
- **Severity**: MEDIUM
- **Pattern**: Vectors, tables, or dynamic fields that can grow without limit
- **Impact**: DoS via gas exhaustion, storage bloat
- **Detection**: `rg "vector::push_back\|table::add\|dynamic_field::add" sources/` without size checks

### 6.2 Dynamic Field Abuse
- **Severity**: HIGH
- **Pattern**: Dynamic fields modified without access control
- **Impact**: Unauthorized metadata or state changes
- **Detection**: `rg "dynamic_field::add\|dynamic_field::remove" sources/`

---

## 7. Sui-Specific Attack Vectors

### 7.1 Kiosk Bypass
- **Severity**: HIGH
- **Pattern**: Assets that should only be traded through kiosk have direct transfer paths
- **Impact**: Bypass transfer policies, royalty evasion
- **Detection**: `rg "transfer::public_transfer" sources/` for types also used in kiosk

### 7.2 Transfer Policy Bypass
- **Severity**: HIGH
- **Pattern**: TransferPolicy not enforced or can be skipped
- **Impact**: Bypass KYC/AML, transfer restrictions
- **Detection**: `rg "TransferPolicy\|TransferRequest" sources/`

### 7.3 UpgradeCap Mishandling
- **Severity**: HIGH
- **Pattern**: UpgradeCap transferred to unauthorized party or not properly managed
- **Impact**: Unauthorized code upgrades, backdoor insertion
- **Detection**: `rg "UpgradeCap\|package::upgrade" sources/`

---

## Attack Vector Matrix (Sui)

| Vector | Severity |
|--------|----------|
| Asset `copy` ability | CRITICAL |
| Asset `drop` ability | CRITICAL |
| Forgeable Witness | CRITICAL |
| Ungated entry function | CRITICAL |
| Object theft | CRITICAL |
| Shared object race | CRITICAL |
| Capability leakage | HIGH |
| PTB composition | HIGH |
| Flash loan manipulation | HIGH |
| Generic type confusion | HIGH |
| Dynamic field abuse | HIGH |
| Kiosk bypass | HIGH |
| Transfer policy bypass | HIGH |
| UpgradeCap mishandling | HIGH |
| Unbounded storage | MEDIUM |
| Stale parameters | MEDIUM |
| First depositor | MEDIUM |
| Rounding exploitation | MEDIUM |

## references/sui/bit-shift-safety.md

---
name: "bit-shift-safety"
description: "Trigger Pattern Always (Sui Move) -- Move VM aborts on shift = bit width - Inject Into Breadth agents, depth-edge-case"
---

# BIT_SHIFT_SAFETY Skill

> **Trigger Pattern**: Always (Sui Move) -- Move VM aborts on shift >= bit width
> **Inject Into**: Breadth agents, depth-edge-case

For every bit shift operation in the protocol:

**BACKGROUND**: The Move VM (shared by Sui and Aptos) aborts the entire transaction if a bit shift operand equals or exceeds the bit width of the type. For `u64`, shifting by 64 or more aborts. For `u128`, shifting by 128 or more aborts. This is NOT a revert with an error code -- it is a VM abort that cannot be caught. On Sui, a PTB (Programmable Transaction Block) abort means ALL commands in the PTB fail, and shared objects that were locked for the transaction are released without state changes.

## 1. Shift Operation Inventory

Enumerate ALL bit shift operations (`<<` and `>>`) across all modules:

| Module | Function | Line | Operation | Type | Bit Width | Shift Amount Source | Validated? |
|--------|----------|------|-----------|------|-----------|--------------------|-----------:|
| {mod} | {func} | {L} | `<<` / `>>` | u8/u64/u128/u256 | 8/64/128/256 | {literal/parameter/computed} | YES/NO |

**Grep pattern**: Search all `.move` files for `<<` and `>>` operators.

**Type-to-width mapping**:
| Type | Max Safe Shift |
|------|---------------|
| `u8` | 7 |
| `u64` | 63 |
| `u128` | 127 |
| `u256` | 255 |

## 2. Shift Amount Source Classification

For each shift operation, classify the shift amount source:

### 2a. Literal Shifts (Low Risk)
```move
let x = value << 32;  // Literal: always safe if < bit width
```
**Check**: Is the literal < bit width of the type? If yes -> SAFE. If no -> compilation may succeed but runtime aborts.

### 2b. Parameter-Derived Shifts (Medium Risk)
```move
public fun shift_by(value: u64, amount: u8): u64 {
    value << (amount as u8)  // Parameter: caller controls shift amount
}
```
**Check**: Is the `amount` parameter validated before the shift? Common patterns:
- `assert!(amount < 64, E_SHIFT_OVERFLOW)` -- explicit validation
- `amount % 64` -- wrapping (changes semantics but prevents abort)
- No validation -- FINDING

### 2c. Computed Shifts (High Risk)
```move
let shift = calculate_precision(decimals);  // Computed: depends on runtime state
let result = base << shift;
```
**Check**: Can the computation ever produce a value >= bit width? Trace the computation to its inputs. If any input is user-controlled or state-derived -> HIGH RISK.

## 3. Abort Impact Analysis

For each unvalidated shift operation, assess the abort impact:

| Function | Called By | Shared Objects Locked? | PTB Context | Abort Impact |
|----------|----------|----------------------|-------------|-------------|
| {func} | {callers} | YES/NO | {typical PTB} | {impact} |

**Sui-specific abort consequences**:
- **PTB abort**: All commands in the Programmable Transaction Block fail atomically. If the shift is in command 3 of a 5-command PTB, commands 1-2 are also rolled back.
- **Shared object locking**: If the aborting function locks shared objects (accessed via `&mut` reference), those objects are temporarily unavailable during consensus. Repeated aborts can cause transient unavailability. This is NOT permanent locking -- Sui releases locks after the transaction fails.
- **Gas consumption**: The sender pays gas for the aborted transaction up to the abort point.
- **Griefing vector**: If an attacker can trigger the abort via a public function with a user-controlled shift amount, they can grief other users by causing their PTBs to abort. This is especially impactful when the aborting function is called as part of a common user flow (deposit, swap, claim).

### 3a. Griefing Scenario Modeling

For each unvalidated shift in a public/entry function:

```
Scenario: Shift Abort Griefing
1. Attacker calls {FUNCTION} with shift amount = {BIT_WIDTH}
2. Move VM aborts the transaction
3. Impact on other users: {IMPACT}
   - If function modifies shared state: other PTBs depending on that state must retry
   - If function is part of a multi-step user flow: user loses gas + must restart
4. Attacker cost: gas for one failed transaction
5. Severity: {based on impact}
```

## 4. Common Vulnerable Patterns

### 4a. Decimal Conversion Shifts
```move
// VULNERABLE: decimals comes from token metadata, could be >= 64
let scale = 1u64 << decimals;
```
**Fix pattern**: `assert!(decimals < 64, E_INVALID_DECIMALS)` or use `math::pow(10, decimals)` instead.

### 4b. Bit Packing / Unpacking
```move
// VULNERABLE if position is not bounds-checked
let field = (packed >> position) & mask;
```
**Check**: Is `position` derived from user input or configuration? If yes and no validation -> FINDING.

### 4c. Fixed-Point Arithmetic
```move
// Common in DeFi: fixed-point multiplication with shift
let result = (a * b) >> PRECISION_BITS;
```
**Check**: Is `PRECISION_BITS` a constant? If yes and < bit width -> SAFE. If computed -> trace source.

### 4d. Loop-Based Shifts
```move
let mut i = 0;
while (i < n) {
    value = value << 1;  // Safe per iteration, but after 64 iterations value = 0 (not abort)
    i = i + 1;
};
```
**Note**: Shifting by 1 repeatedly does NOT abort (shift amount is always 1). But the value overflows silently to 0 after bit-width iterations. Check if this silent overflow causes logic errors.

## 5. Validation Pattern Verification

For each shift operation that IS validated, verify the validation is correct:

| Function | Validation | Correct? | Edge Case |
|----------|-----------|----------|-----------|
| {func} | `assert!(n < 64)` | YES | n=63 is max safe |
| {func} | `assert!(n <= 64)` | **NO** | n=64 aborts |
| {func} | `n % 64` | SAFE but semantic change | shift by 0 when n=64 |

**Common validation errors**:
- Off-by-one: `<= bit_width` instead of `< bit_width`
- Wrong bit width: validating against 64 for a `u128` shift (allows 64-127 to abort)
- Missing cast: `amount` is `u64` but shift operand must be `u8` -- does the cast truncate?

## 6. Cross-Function Shift Propagation

Trace shift amounts across function boundaries:

```
entry_function(user_input: u64)
  -> helper_a(derived_value)  // derived_value = user_input * 2
    -> helper_b(shift_amount) // shift_amount = derived_value + offset
      -> actual_shift: value << shift_amount  // Is shift_amount < bit_width?
```

**For each chain**: Can ANY combination of valid inputs to the entry function produce a shift amount >= bit width at the actual shift site? Document the full trace.

## Finding Template

```markdown
**ID**: [BS-N]
**Severity**: [HIGH if public function, MEDIUM if restricted caller, LOW if constant shift]
**Step Execution**: check1,2,3,4,5,6 | X(reasons) | ?(uncertain)
**Rules Applied**: [R4:Y, R10:Y, ...]
**Depth Evidence**: [BOUNDARY:shift=bit_width], [TRACE:user_input->shift_amount->abort]
**Location**: module::function:LineN
**Title**: Unvalidated bit shift in [function] causes VM abort on [condition]
**Description**: [Specific shift operation, source of shift amount, why it can reach bit width]
**Impact**: [Transaction abort, shared object locking, griefing potential, gas waste]
```

---

## Step Execution Checklist (MANDATORY)

> **CRITICAL**: You MUST report completion status for ALL sections. Findings with incomplete sections will be flagged for depth review.

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Shift Operation Inventory | YES | Y/X/? | Grep all `.move` files |
| 2. Shift Amount Source Classification | YES | Y/X/? | For each shift |
| 3. Abort Impact Analysis | IF unvalidated shifts found | Y/X(N/A)/? | |
| 3a. Griefing Scenario Modeling | IF unvalidated in public fn | Y/X(N/A)/? | |
| 4. Common Vulnerable Patterns | YES | Y/X/? | Check all 4 sub-patterns |
| 5. Validation Pattern Verification | IF validated shifts exist | Y/X(N/A)/? | Off-by-one check |
| 6. Cross-Function Shift Propagation | IF shift amount crosses functions | Y/X(N/A)/? | |

### Cross-Reference Markers

**After Section 1** (Shift Inventory):
- IF zero shift operations found -> mark skill as N/A, skip remaining sections
- IF shifts found in math/fixed-point libraries -> prioritize Section 4c

**After Section 3** (Abort Impact):
- IF abort affects shared objects -> cross-reference with ABILITY_ANALYSIS Section 2b (shared object analysis)
- IF abort is in a hot potato consumption path -> cross-reference with ABILITY_ANALYSIS Section 5 (hot potato enforcement) -- abort before consumption = permanent PTB failure

**After Section 6** (Cross-Function Propagation):
- IF any chain reaches bit width with valid inputs -> FINDING (minimum Medium)
- Tag: `[BOUNDARY:shift={bit_width}]`, `[TRACE:input_path->abort_site]`

## references/sui/centralization-risk.md

---
name: "centralization-risk"
description: "Trigger Pattern Protocol has privileged capabilities (AdminCap, OwnerCap, UpgradeCap, TreasuryCap, custom caps) - Inject Into Breadth agents (optional), depth-state-trace"
---

# Skill: CENTRALIZATION_RISK (Sui)

> **Trigger Pattern**: Protocol has privileged capabilities (AdminCap, OwnerCap, UpgradeCap, TreasuryCap, custom caps)
> **Inject Into**: Breadth agents (optional), depth-state-trace
> **Finding prefix**: `[CR-N]`
> **Rules referenced**: R2, R6, R9, R10, R13
> **Required**: NO (recommended when protocol has 3+ distinct privileged capability types)

Covers: single points of failure, privilege escalation, capability object management, external governance dependencies, emergency powers. On Sui, centralization risk has unique dimensions: capability objects (AdminCap) are first-class owned objects that can be transferred, UpgradeCap controls full package replacement, TreasuryCap controls token supply, and shared objects can be the target of admin-gated mutations. The ownership and lifecycle of capability objects IS the access control model.

---

## Trigger Patterns

```
AdminCap|OwnerCap|UpgradeCap|TreasuryCap|GovernanceCap|OperatorCap|PauserCap|MinterCap|
Cap\b|_cap|admin|authority|privilege
```

---

## Step 1: Capability Inventory

Enumerate ALL capability objects and ALL functions requiring capabilities:

| # | Capability Type | Module | Abilities | Created Where | Holder | What It Controls | Impact If Lost/Stolen |
|---|----------------|--------|-----------|---------------|--------|------------------|----------------------|
| 1 | {CapType} | {module} | {key, store, ...} | {init or func} | {address/shared} | {list functions} | {worst case} |

**Key question for each capability**: Is it OWNED (by a single address) or SHARED (accessible via reference in any transaction)?
- **Owned AdminCap**: Only the owner can pass it as a tx argument. Strongest access control, but single point of failure.
- **Shared config with admin field**: Admin address stored in shared object. More flexible but requires `assert!(sender == admin)` checks -- verify these are present on ALL admin functions.
- **Shared capability object**: DANGEROUS -- anyone can pass a shared `AdminCap` as a transaction argument without ownership.

**Categorize each by impact**:
- **FUND_CONTROL**: Can move, lock, or destroy user funds (e.g., emergency withdraw, treasury drain)
- **PARAMETER_CONTROL**: Can change fees, rates, thresholds, delays (e.g., set_fee, set_max_leverage)
- **OPERATIONAL_CONTROL**: Can pause, unpause, add/remove pools, whitelist/blacklist
- **UPGRADE_CONTROL**: UpgradeCap -- controls package upgrades, policy changes
- **MINT_CONTROL**: TreasuryCap -- controls token supply (mint/burn)

**Sui-specific ability checks**:
- Does the capability have `store` ability? If YES -> anyone holding it can `public_transfer` it, meaning the privilege is freely transferable. Is this intentional?
- Does the capability have `drop` ability? If YES -> it can be silently discarded. For AdminCap, dropping it means admin functions become permanently uncallable. For UpgradeCap, dropping makes the package permanently immutable.
- Is the capability created ONLY in `init()`? If created elsewhere, can unauthorized parties mint new capabilities?

---

## Step 2: Capability Hierarchy and Separation

Map the capability hierarchy:

| Capability | Created By | Can Create Other Caps? | Transferable (has `store`)? | Destructible (has `drop`)? | Timelock/Delay? |
|-----------|-----------|----------------------|---------------------------|---------------------------|-----------------|
| {cap} | {init / admin_func} | YES/NO | YES/NO | YES/NO | YES/NO |

**Check**:
- [ ] Are FUND_CONTROL and UPGRADE_CONTROL separated into different capability types?
- [ ] Does any single capability type grant both PARAMETER_CONTROL and FUND_CONTROL?
- [ ] Are capability transfers behind timelocks or governance mechanisms?
- [ ] Can capabilities be destroyed, and what happens when they are?
- [ ] Is there a master capability that can create all other capabilities?

**Sui-specific separation patterns**:
- Best practice: UpgradeCap held by governance multisig, AdminCap held by operations team, TreasuryCap held by treasury
- Anti-pattern: single `init` function creates ALL caps and transfers them to `tx_context::sender()` -- single point of failure at deployment
- Two-step transfer: propose new admin -> new admin accepts. Prevents accidental transfer to wrong address.

### UpgradeCap Analysis (CRITICAL)

| Package | UpgradeCap Holder | Upgrade Policy | Destroyed? | Risk Level |
|---------|------------------|---------------|-----------|------------|
| {package_id} | {address or description} | {compatible/additive/dep_only} | YES (immutable) / NO | {assessment} |

**Risk levels**:
- **UpgradeCap destroyed (`make_immutable`)**: No upgrade risk.
- **UpgradeCap held by governance multisig with timelock**: Low risk.
- **UpgradeCap held by multisig (no timelock)**: Low-Medium risk.
- **UpgradeCap held by single address**: **CRITICAL** risk -- one compromised key replaces entire package. All shared objects now interact with attacker code.
- **UpgradeCap stored in shared object**: Check access control carefully. If extraction is possible -> same as single address risk.

---

## Step 3: Single Points of Failure

For each capability type:

| Capability | Key Compromise Impact | Current Protection | Residual Risk |
|-----------|----------------------|-------------------|---------------|
| {cap} | {what attacker can do with it} | {multisig holder? timelock wrapper?} | {what remains} |

### Sui-Specific SPOF Analysis

| Risk | Description | Severity |
|------|-------------|----------|
| **UpgradeCap compromise** | Attacker publishes malicious upgrade. All shared objects now interact with attacker code. ALL user funds at risk. | CRITICAL if single address, HIGH if multisig without timelock |
| **AdminCap compromise** | Attacker calls admin functions: drain pools, change parameters, pause protocol. | HIGH if AdminCap controls fund extraction |
| **TreasuryCap compromise** | Attacker mints unlimited tokens, diluting all holders. | HIGH if supply-sensitive protocol |
| **AdminCap with `store`** | Holder (or compromised key) can transfer AdminCap to anyone via `public_transfer`. New holder has full admin access. | Adds transfer risk to any compromise scenario |
| **AdminCap with `drop`** | Admin can accidentally destroy the capability. Admin functions become permanently uncallable. | MEDIUM -- permanent loss of admin access (Rule 9 if admin functions needed for user fund recovery) |
| **Phantom ownership** | Capability transferred to an address nobody controls (e.g., `@0x0`). Object is permanently inaccessible -- equivalent to destroying it. | Same as destruction if holding value or needed for operations |

**Severity assessment**:
- Single address with FUND_CONTROL or UPGRADE_CONTROL -> **HIGH** (minimum)
- Multisig holds capability + timelock -> **LOW** (but document)
- UpgradeCap destroyed + no admin fund extraction -> **INFO**

---

## Step 4: External Governance Dependencies

Identify parameters or behaviors controlled by EXTERNAL governance:

| Dependency | External Entity | What They Control | Protocol Impact If Changed | Notification? |
|------------|----------------|-------------------|---------------------------|---------------|
| {dep} | {entity} | {parameter/behavior} | {impact} | YES/NO |

**Sui-specific external governance**:
- **Sui framework upgrades**: Validators upgrade `sui::*` packages via governance. Can framework changes break this protocol?
- **Oracle provider changes**: If protocol reads from oracle shared object, oracle admin can change prices, feeds, or parameters
- **DeFi protocol governance**: External pools, vaults, or DEXes may change parameters
- **Bridge governance**: Wormhole guardian set rotation, Sui Bridge committee changes
- **Dependency package upgrades**: If a dependency has active UpgradeCap, its owner can publish new versions. Our package pins to specific version at compile time, but compatible upgrades preserve types that we import.

**Check**:
- Can external governance changes break protocol invariants?
- Does the protocol have circuit breakers for external changes?
- **Does the protocol verify external package addresses at call sites?** Types from compatible-upgraded packages remain the same, but behavior may change.

---

## Step 5: Emergency Powers

Document emergency/pause capabilities:

| Emergency Function | Required Capability | What It Affects | Recovery Path | Time to Recover |
|-------------------|-------------------|-----------------|---------------|-----------------|
| {func} | {cap_type} | {scope: all operations / specific pool} | {how to resume} | {estimate} |

### Sui Emergency Patterns

| Pattern | Description | Risk |
|---------|-------------|------|
| **Global pause field** | Shared config object has `paused: bool`. All user functions check it. | Standard -- check: can users withdraw when paused? |
| **Capability destruction** | Admin destroys their own capability to "renounce" control. | Irreversible -- if needed later for recovery, funds stranded |
| **Object freeze** | Admin calls `transfer::public_freeze_object` on a config. Permanent immutability. | If done to wrong object, permanent loss of admin access |
| **Package policy tightening** | UpgradeCap holder restricts policy (compatible -> additive -> immutable). | Good for security, but irreversible. Cannot loosen policy. |

**Check**:
- [ ] Can pausing strand user funds permanently? (Rule 9 -- stranded asset severity floor: minimum MEDIUM)
- [ ] Is there a maximum pause duration or automatic unpause?
- [ ] Can users emergency-withdraw during pause?
- [ ] What happens if the PauserCap/AdminCap is lost or destroyed?
- [ ] Can the protocol be permanently bricked by destroying a critical capability?
- [ ] If no exit during pause -> apply Rule 9 (minimum MEDIUM)

---

## Output Schema

```markdown
## Finding [CR-N]: Title

**Verdict**: CONFIRMED / PARTIAL / REFUTED
**Step Execution**: check1,2,3,4,5 | skip(reason) | uncertain
**Rules Applied**: [R2:___, R6:___, R9:___, R10:___, R13:___]
**Severity**: Critical/High/Medium/Low/Info
**Location**: sources/{module}.move:LineN

**Centralization Type**: FUND_CONTROL / PARAMETER_CONTROL / OPERATIONAL_CONTROL / UPGRADE_CONTROL / MINT_CONTROL
**Affected Capability**: {cap_type}
**Mitigation Present**: {multisig / timelock / UpgradeCap destroyed / governance / NONE}

**Description**: What is wrong
**Impact**: What can happen if capability is compromised, lost, or holder acts maliciously
**Recommendation**: How to mitigate (destroy UpgradeCap, use multisig, add timelock, remove `store` ability, wrap in governance)
```

---

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Capability Inventory (all cap-gated functions) | YES | | Owned vs shared, abilities checked |
| 2. Capability Hierarchy and Separation | YES | | `store`/`drop` analysis, UpgradeCap assessment |
| 3. Single Points of Failure (per capability) | YES | | |
| 4. External Governance Dependencies | YES | | |
| 5. Emergency Powers and Recovery Paths | YES | | |

### Cross-Reference Markers

**After Step 1**: Cross-reference with ABILITY_ANALYSIS Section 4 (Capability Pattern Audit) -- capabilities with `store` enable unrestricted transfer.

**After Step 2**: If UpgradeCap held by single address -> immediate finding (minimum HIGH).

**After Step 3**: If AdminCap has `drop` and is needed for fund recovery -> Rule 9 stranded asset finding.

**After Step 5**: If no emergency withdraw exists AND pause is possible -> Rule 9 stranded asset finding.

**After Step 5**: If protocol claims trustlessness but retains UpgradeCap/AdminCap -> Rule 13 anti-normalization finding.

If any step skipped, document valid reason (N/A, no external governance, no emergency functions, single capability only).

## references/sui/cross-chain-timing.md

---
name: "cross-chain-timing"
description: "Type Thought-template (instantiate before use) - Trigger Pattern bridge|wormhole|axelar|layerzero|sui_bridge|cross_chain|relay|vaa|guardian|emitter|ccip"
---

# Skill: Cross-Chain Timing Analysis (Sui)

> **Type**: Thought-template (instantiate before use)
> **Trigger Pattern**: `bridge|wormhole|axelar|layerzero|sui_bridge|cross_chain|relay|vaa|guardian|emitter|ccip`
> **Inject Into**: Breadth agents, depth-external
> **Finding prefix**: `[CCT-N]`
> **Rules referenced**: R1, R2, R4, R8, R10, R16
> **Research basis**: Multi-block arbitrage windows, bridge latency exploitation

Covers: cross-chain message verification, timing asymmetry between Sui and other chains, object creation requirements for bridged assets, nonce/sequence replay protection, and cross-chain price relay staleness.

Sui's consensus model produces checkpoints every ~0.5-2 seconds and epochs every ~24 hours. Cross-chain messaging relies on bridge protocols (Wormhole, Axelar, Sui Bridge native) that verify Sui checkpoints before relaying messages. Sui's fast finality (~2-3s checkpointed) creates timing asymmetry with slower chains (Ethereum ~12min, rollups 10-60min).

---

## Trigger Patterns
```
bridge|wormhole|axelar|layerzero|sui_bridge|cross_chain|vaa|relay|messenger|
send_message|receive_message|bridge_transfer
```

---

## Step 1: Identify Cross-Chain Messaging Infrastructure

Find all cross-chain messaging calls in {CONTRACTS}:

| # | Function | Module | Direction | Bridge Protocol | State Synced | Trigger |
|---|----------|--------|-----------|-----------------|-------------|---------|
| 1 | {func} | {module} | OUTBOUND/INBOUND | {protocol} | {what state} | {when sent} |

For each call, determine:
- What state is being synced? (rates, balances, epochs, totals, oracle prices)
- What triggers the sync? (every operation, periodic, manual keeper call)
- What bridge/messenger is used? (Wormhole VAA, Axelar GMP, Sui Bridge native, custom relay)
- Is the message authenticated? (VAA signatures, validator attestations, committee signatures)

### Wormhole-Specific Inventory (Sui)
If Wormhole is detected:

| Component | Function/Object | Purpose | Location |
|-----------|----------------|---------|----------|
| VAA Verification | `vaa::parse_and_verify()` | Guardian signature verification | {module:line} |
| Message Posting | `publish_message()` | Send message from Sui | {module:line} |
| Token Bridge | `complete_transfer()` / `create_wrapped()` | Token bridging | {module:line} |
| Emitter Object | Shared or owned emitter state | Message source identity | {module:line} |

### Sui Bridge (Native) Inventory
If the native Sui Bridge is detected:

| Component | Function/Object | Purpose | Location |
|-----------|----------------|---------|----------|
| Bridge Committee | Shared committee object | Validator attestation | {module:line} |
| Message Verification | `verify_and_execute()` | Committee signature check | {module:line} |
| Token Transfer | Bridge treasury operations | Lock/unlock bridged tokens | {module:line} |

**Sui-specific outbound/inbound**:
- Outbound messages: typically emitted as events or written to shared objects for relayer pickup
- Inbound messages: typically processed by a function receiving a VAA or equivalent proof
- `clock::timestamp_ms()` provides millisecond timestamps -- check if timestamp freshness is validated on message receipt

---

## Step 2: Cross-Chain Message Verification Audit

For EACH inbound cross-chain message consumed by the protocol:

### 2a. Wormhole VAA Verification Checklist (Sui)

| # | Check | Status | Location | Notes |
|---|-------|--------|----------|-------|
| 1 | Guardian signature count >= quorum (13/19) | YES/NO | {line} | Does protocol verify `guardian_set_index` is current? |
| 2 | Guardian set is current (not expired) | YES/NO | {line} | Old guardian sets may be compromised |
| 3 | Emitter chain ID validated | YES/NO | {line} | Reject messages from unexpected source chains |
| 4 | Emitter address validated | YES/NO | {line} | Reject messages from unexpected contracts |
| 5 | Sequence number replay check | YES/NO | {line} | Each VAA should be processed exactly once |
| 6 | Consistency level validated | YES/NO | {line} | `finalized` vs `confirmed` |
| 7 | Payload format validated | YES/NO | {line} | Malformed payload handling |
| 8 | VAA object authenticity | YES/NO | {line} | Is VAA object from the actual Wormhole package? Type check on package address. |

**Critical**: Missing checks 1-5 = **CRITICAL** (arbitrary cross-chain message injection). Missing checks 6-8 = **HIGH**.

**Sui-specific**: On Sui, Wormhole VAAs are represented as objects. Verify the VAA object type comes from the authentic Wormhole package (check package address) -- an attacker could deploy a fake Wormhole package with matching type names.

### 2b. Generic Bridge Verification

For non-Wormhole bridges:

| # | Check | Status | Location | Notes |
|---|-------|--------|----------|-------|
| 1 | Message source authenticated (signatures/proofs) | YES/NO | {line} | |
| 2 | Source chain ID validated | YES/NO | {line} | |
| 3 | Source contract/address validated | YES/NO | {line} | |
| 4 | Replay protection (nonce/sequence/Table lookup) | YES/NO | {line} | |
| 5 | Message freshness (timestamp check vs `clock::timestamp_ms`) | YES/NO | {line} | |
| 6 | Relayer authorization (if applicable) | YES/NO | {line} | |

---

## Step 3: Timing Window Analysis

### 3a. Finality Asymmetry Model

| Chain | Optimistic Finality | Checkpointed Finality | Protocol Assumes |
|-------|--------------------|-----------------------|-----------------|
| Sui | ~400ms (execution) | ~2-3s (checkpoint) | {which level?} |
| {Remote Chain} | {time} | {time} | {which level?} |
| **Asymmetry Window** | -- | -- | **{max delay between chains}** |

**Critical question**: When Sui processes a message about remote chain state, how old can that state be? Compute: `max_staleness = remote_finality + bridge_relay_delay + sui_processing_time`

### 3b. Stale State Usage Trace

For each piece of state synced cross-chain:

| State Variable | Source Chain | Sync Trigger | Max Staleness | Sui Functions Using It | Fresh Required? |
|----------------|-------------|-------------|--------------|----------------------|----------------|
| {state} | {chain} | {event/periodic/manual} | {time} | {list functions} | YES/NO |

For each dependent function on Sui:
- Is fresh state required or is stale acceptable?
- What decisions are made with potentially stale data?
- Is there a staleness check (e.g., comparing `clock::timestamp_ms()` against message timestamp)?

**Sui-specific checks**:
- Are there epoch-boundary effects? (Sui epoch changes can affect staking rewards, validator sets)
- Is the synced state stored in a shared object that other transactions can race against?
- No mempool in Sui: front-running model differs from EVM (but sequencing attacks via validator collusion possible for shared objects)

### 3c. Sui-to-Remote Timing Attack

Sui's fast finality means actions on Sui are visible almost immediately, but take time to propagate to remote chains:

```
1. Attacker acts on Sui (visible in ~2-3s checkpoint)
2. Sui message posted via bridge (begins relay)
3. TIMING WINDOW: Remote chain does not yet know about Sui action
4. Attacker acts on remote chain using pre-Sui-action state
5. Bridge message arrives on remote chain -- state updates
6. Attacker profited from acting on both chains during asymmetry
```

### 3d. Remote-to-Sui Timing Attack

```
1. State changes on remote chain (e.g., price moves, governance action)
2. Bridge message relay begins (latency: {estimate})
3. TIMING WINDOW: Sui still uses old remote state
4. Attacker acts on Sui using stale remote state (low tx cost)
5. Bridge message arrives on Sui -- state updates
6. Attacker profited from Sui action with stale state
```

---

## Step 4: Object Creation Requirements

Cross-chain operations on Sui have unique object requirements:

| # | Check | Status | Notes |
|---|-------|--------|-------|
| 1 | Recipient object/account exists before transfer arrival? | YES/NO | Who creates it? Who pays gas? |
| 2 | Are wrapped/bridged coin types created correctly? | YES/NO | `TreasuryCap` held by bridge, OTW consumed correctly? |
| 3 | What happens if recipient cannot receive the object? | {revert/queue/escrow} | Reverted transfers may be lost on source chain |
| 4 | Can attacker manipulate shared objects between message arrival and execution? | YES/NO | Consensus ordering is non-deterministic from user's perspective |
| 5 | Are bridged asset objects shared or owned? | {shared/owned} | Shared: contention risk. Owned: only recipient can use. |
| 6 | Is there a claim/complete mechanism or auto-delivery? | {claim/auto} | Claim: user must submit tx. Auto: relayer delivers. |

**Sui-specific**: Bridged tokens on Sui are typically `Coin<BridgedType>` where `BridgedType` was registered by the bridge via OTW. Verify: is the `TreasuryCap` for bridged tokens held exclusively by the bridge? Can anyone else mint bridged tokens? If TreasuryCap is stored in a shared object, check that access control prevents unauthorized minting.

---

## Step 5: Nonce and Sequence Management

| # | Check | Status | Location | Notes |
|---|-------|--------|----------|-------|
| 1 | Replay protection exists | YES/NO | {line} | Method: {Table<Hash,bool> / dynamic field / counter / unique object per message} |
| 2 | Replay check is BEFORE state changes | YES/NO | {line} | If after: partial replay possible |
| 3 | Out-of-order messages handled | YES/NO | {line} | Strict ordering vs any-order processing |
| 4 | Sequence gaps handled | YES/NO | {line} | What if message N+1 arrives before N? |
| 5 | Replay storage bounded | YES/NO | {line} | Table/Bag may grow unbounded (DoS via storage cost) |
| 6 | Double-spend across chains | YES/NO | {line} | Same asset spent on both chains during relay |

**Sui replay patterns**:
- **Table<Hash, bool>**: Store processed message hashes. Reliable but Table grows unbounded.
- **Dynamic field per message**: Add dynamic field with message ID as key. Same growth concern.
- **Unique object per message**: Create an object per processed message (exists = processed). Objects persist permanently on-chain.
- **Counter**: Only process sequence N if N-1 was processed. Enforces ordering but blocks on gaps.

---

## Step 6: Cross-Chain Price Relay Audit

If oracle prices are relayed cross-chain:

| # | Check | Status | Notes |
|---|-------|--------|-------|
| 1 | Price freshness validated on Sui side (`clock::timestamp_ms`) | YES/NO | Max acceptable age? |
| 2 | Price source authenticated (bridge signature) | YES/NO | Can fake price be relayed? |
| 3 | Price deviation bounds | YES/NO | Max delta from last known price? |
| 4 | Fallback if relay is delayed/offline | YES/NO | What happens to price-dependent operations? |
| 5 | Flash loan on source chain can manipulate relayed price | YES/NO | Is source price spot or TWAP? |

**Staleness calculation**: `relay_staleness = source_price_age + bridge_latency + sui_processing`

If `relay_staleness > acceptable_threshold` at worst case, price is stale. Apply Rule 16 (Oracle Integrity).

---

## Step 7: Quantify Arbitrage Viability

```
1. Attacker monitors {SOURCE_CHAIN} for state changes at {MONITOR_POINT}
2. State change triggers sync message (latency window opens: ~{LATENCY} minutes)
3. Attacker executes on Sui at {EXPLOIT_FUNCTION} using stale {STALE_STATE}
   - Sui transaction cost is very low (<$0.01 per tx)
   - PTB allows multi-step atomic exploitation
4. Sync message arrives on Sui, state updates
5. Profit = {PROFIT_FORMULA}
6. Cost = bridge_fees + Sui_gas + capital_lockup_cost
7. Viable if: profit > cost AND repeatable
```

**Sui cost model**: Sui gas is paid in SUI, typically very low (<$0.01 per tx). Cost barrier is primarily bridge fees and capital requirements. Low gas makes small-margin attacks more viable than on EVM.

---

## Key Questions (must answer all)

1. What is the realistic sync latency for {BRIDGE_PROTOCOL} on Sui? (cite documentation)
2. Can an attacker monitor {SOURCE_CHAIN} and exploit stale state on Sui (or vice versa) before sync completes?
3. What is the maximum {STALE_STATE} change during normal operation within the sync window?
4. Is this attack repeatable or one-time?
5. Does the protocol validate message timestamps against `clock::timestamp_ms()`?
6. Are bridged token TreasuryCaps exclusively held by the bridge?
7. Is replay protection complete (covers all message types, all chains)?
8. Are cross-chain prices validated for freshness AND deviation bounds?

---

## Common False Positives

- **Monotonic state**: If synced state only increases, arbitrage may not be profitable in both directions
- **Negligible delta**: If max delta during sync window is <0.1%, may not be economically viable after costs
- **Rate limiting**: If operations have cooldowns longer than sync latency, window may not be exploitable
- **Timestamp freshness check**: If protocol compares message timestamp against `clock::timestamp_ms()` and rejects stale messages, window is bounded
- **Epoch-aligned sync**: If sync happens once per epoch (~24h) and this is documented/intended, staleness within an epoch may be by design
- **Bridge-level protections**: Some bridges have rate limiting or value caps that bound exploitation

---

## Instantiation Parameters

```
{CONTRACTS}           -- List of modules to analyze
{BRIDGE_PROTOCOL}     -- Specific bridge (Wormhole, Axelar, Sui Bridge, custom)
{SYNC_POINT}          -- Function where inbound sync occurs
{DEPENDENT_FUNCTIONS} -- Functions that read synced state
{SOURCE_CHAIN}        -- Chain where state originates
{DEST_CHAIN}          -- Chain where stale state is exploited (may be Sui)
{MONITOR_POINT}       -- What attacker monitors on source chain
{EXPLOIT_FUNCTION}    -- Function attacker calls using stale state
{STALE_STATE}         -- Specific state variable that becomes stale
{PROFIT_FORMULA}      -- (new_value - old_value) * position_size
{MAX_DELTA}           -- Maximum observed state change during sync window
{LATENCY}             -- Estimated bridge latency in minutes
```

---

## Output Schema

| Field | Required | Description |
|-------|----------|-------------|
| bridge_inventory | yes | All cross-chain messaging infrastructure |
| verification_audit | yes | Message verification completeness |
| timing_windows | yes | Asymmetry windows with duration estimates |
| object_creation | yes | Bridged asset object requirements and failure modes |
| replay_protection | yes | Nonce/sequence management assessment |
| price_relay_audit | if applicable | Cross-chain price freshness and manipulation risk |
| arbitrage_viability | yes | Quantified attack profitability or NOT_VIABLE |
| finding | yes | CONFIRMED / REFUTED / CONTESTED |
| evidence | yes | Code locations with line numbers |

---

### Denylist Enforcement Lag
- **Denylist enforcement lag**: For cross-chain denylist/blocklist updates, check the window between message receipt and enforcement. Can transactions from denylisted addresses execute during this window? Are in-flight operations for denylisted addresses cancelled or allowed to complete?

---

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Identify Cross-Chain Messaging Infrastructure | YES | | All cross-chain calls enumerated |
| 2. Cross-Chain Message Verification Audit | YES | | VAA/message verification complete |
| 3. Timing Window Analysis (both directions) | YES | | Cite bridge documentation |
| 4. Object Creation Requirements | YES | | Bridged token TreasuryCap security |
| 5. Nonce and Sequence Management | YES | | Replay storage boundedness |
| 6. Cross-Chain Price Relay Audit | IF price relay detected | | |
| 7. Quantify Arbitrage Viability | YES | | Profit vs cost with real numbers |

### Cross-Reference Markers

**After Step 2**: If message verification is incomplete -> immediate finding, do not wait for timing analysis.

**After Step 3**: Feed timing windows to TEMPORAL_PARAMETER_STALENESS skill for parameters cached across chain boundaries.

**After Step 4**: If bridged token TreasuryCap is not exclusively held by bridge -> cross-reference with TYPE_SAFETY Coin/Balance section.

**After Step 6**: Feed price staleness findings to ORACLE_ANALYSIS if applicable.

If any step skipped, document valid reason (N/A, no cross-chain messaging, single chain only).

## references/sui/dependency-audit.md

---
name: "dependency-audit"
description: "Trigger Pattern EXTERNAL_LIB flag (third-party Move dependencies detected in Move.toml beyond Sui framework) - Inject Into Breadth agents, depth-external"
---

# Skill: DEPENDENCY_AUDIT (Sui/Move)

> **Trigger Pattern**: EXTERNAL_LIB flag (third-party Move dependencies detected in Move.toml beyond Sui framework)
> **Inject Into**: Breadth agents, depth-external
> **Finding prefix**: `[DEP-N]`
> **Rules referenced**: R1, R4, R8, R10

Move's dependency model is package-based: `Move.toml` declares dependencies with git URLs and revisions. Unlike EVM's compiled-and-deployed model where dependencies are inlined at compile time, Sui Move packages can depend on other PUBLISHED packages (on-chain) or source packages (compiled together). Third-party math libraries, utility packages, and protocol SDKs are common dependency vectors.

**STEP PRIORITY**: Steps 3 (Critical Function Audit, especially Step 4 Math Library Audit) and 5 (Shared Object Dependencies) are where HIGH/CRITICAL severity findings most commonly hide. The Cetus hack originated from a custom math library bit shift bug. Do NOT rush these steps.

---

## Trigger Patterns

```
[dependencies]|git\s*=|subdir\s*=|rev\s*=|published-at|math|utils|library|helpers|common
```

---

## Step 1: Dependency Inventory

Parse `Move.toml` and build a complete dependency tree. Categorize:

| # | Dependency Name | Source Type | Source URL/Address | Version/Rev Pinned? | Trust Level | Upgrade Risk |
|---|----------------|------------|-------------------|---------------------|-------------|-------------|
| 1 | Sui | Framework | sui framework | Validator-controlled | TRUSTED | Framework upgrade by validators |
| 2 | MoveStdlib | Framework | std library | Validator-controlled | TRUSTED | Framework upgrade by validators |
| 3 | {third_party} | Git source | {url} | YES (rev={hash}) / NO (branch) | MUST_AUDIT | {describe} |
| 4 | {on_chain_dep} | Published | {on-chain address} | YES (version pinned) / NO | MUST_AUDIT | {describe} |
| 5 | {protocol_own} | Local path | {path} | N/A (in scope) | IN_SCOPE | N/A |

**Trust classification**:
- **TRUSTED**: Sui framework packages (`sui`, `std`). Audited by Mysten Labs, upgraded by validator governance. Minimal audit needed (but check for version-specific quirks).
- **MUST_AUDIT**: Third-party packages. MUST analyze critical functions used by the protocol.
- **IN_SCOPE**: Protocol's own packages. Full audit in main analysis.

---

## Step 2: Package Immutability Check

For each third-party dependency, assess immutability and upgrade risk:

| Dependency | Pinned to Specific Rev? | Published On-Chain? | UpgradeCap Status | Upgrade Policy | Risk |
|-----------|------------------------|--------------------|--------------------|---------------|------|
| {dep} | YES (rev: {hash}) / NO (branch: main) | YES/NO | Destroyed (immutable) / Held by {who} / UNKNOWN | {compatible/additive/dep_only/immutable} | {assess} |

**Source dependencies** (compiled together):
- Pinned to specific git revision -> code is fixed at that commit. Safe from upstream changes.
- Pinned to a branch (e.g., `main`) -> upstream pushes automatically affect next compilation. **FINDING**: unpinned dependency.
- No `rev` field -> defaults to latest on default branch. Highest risk.

**Published on-chain dependencies** (referenced via `published-at`):
- Immutable package (UpgradeCap destroyed) -> behavior cannot change. Safe.
- Package with active UpgradeCap + `compatible` policy -> behavior CAN change.
- Your package pins to a specific version at compile time. If dependency publishes V2, you still use V1.
- **Risk**: When YOU upgrade (recompile), you may pull in dependency's latest version unknowingly.

**Known upgrade history**: Has the dependency been upgraded before? How many versions exist? Frequent upgrades indicate active development but also active change risk.

**Checklist**:
- [ ] Every third-party dependency is pinned to a specific git revision (not a branch)
- [ ] Published dependencies are either immutable or their upgrade policy is documented
- [ ] No dependency uses a `latest` or `main` branch reference

---

## Step 3: Transitive Dependency Risk

Map the full dependency tree:

| Dependency A | Depends On | Dep B Audited? | Dep B Upgrade Risk | Version Conflict? |
|-------------|-----------|---------------|-------------------|------------------|
| {dep_A} | {dep_B, dep_C} | YES/NO | {describe} | YES/NO |

**Transitive dependency risks**:
- A -> B -> C: If C has vulnerability, A is affected even though A does not directly import C
- Version conflicts: If A depends on C v1 and B depends on C v2, Move compilation may fail. Sui resolves diamond dependencies by requiring all paths to agree on the same version.
- Transitive upgrade: If B upgrades and changes its dependency on C, your next recompile may pull different C code.

**If Dep B upgrades, does it affect us through Dep A?**
- Only if we recompile our package (Sui does not dynamically resolve dependencies)
- But: if Dep B is an on-chain published package that Dep A calls via CPI-equivalent, behavior changes immediately after Dep B upgrades

---

## Step 4: Math Library Audit (CRITICAL -- Cetus Precedent)

> **Historical context**: A major DeFi exploit targeted a bug in a custom bit shift helper function in a math library. This step is MANDATORY for any custom math/arithmetic library in the dependency tree.

For any custom math/arithmetic library dependency:

### 4a. Bit Shift Operation Audit (MR2)

Trace ALL bit shift operations (`<<`, `>>`) in the math library:

| # | Function | Shift Operation | Shift Amount Source | Bounds Checked? | Overflow Possible? |
|---|----------|----------------|--------------------|-----------------|--------------------|
| 1 | {func} | `value << amount` | {parameter / constant / computed} | YES/NO | YES/NO |

**Move bit shift rules**:
- `<<` and `>>` do NOT abort if shift amount >= bit width -- they produce 0
- Custom bit shift helpers MUST validate shift amount < bit width
- If shift amount comes from user input or computation, it must be bounds-checked

**Specific checks**:
- [ ] Are ALL shift amounts validated to be < bit width of the operand type?
- [ ] Do custom bit shift helpers correctly handle edge cases (shift amount >= bit width, zero inputs, overflow)?
- [ ] Can intermediate computation produce a shift amount >= bit width?
- [ ] Are there any bit manipulation patterns that assume shift produces a specific non-zero result?

### 4b. Overflow/Underflow Audit

| # | Function | Operation | Input Range | Overflow Possible? | Handling |
|---|----------|-----------|------------|--------------------|---------|
| 1 | {func} | `a * b` | {describe} | YES if a,b > sqrt(MAX_U128) | abort (safe) / wrapping (DANGEROUS) |

**Move arithmetic safety**:
- Default `+`, `-`, `*` abort on overflow/underflow -- safe
- But: custom math libraries may use bitwise operations to implement unchecked arithmetic for gas optimization
- `as` casts between integer types abort on overflow (e.g., `(x as u64)` where x > MAX_U64)
- Fixed-point: `(a * b) / SCALE` -- intermediate `a * b` may overflow u128 even if final result fits in u64

### 4c. Rounding and Precision

| # | Function | Rounding Direction | Consistent? | Impact if Wrong Direction |
|---|----------|-------------------|-------------|--------------------------|
| 1 | {mul_div} | {up / down / nearest / truncation} | YES/NO | {describe: e.g., attacker extracts extra dust per operation} |

**Check**: For every division operation in the math library:
- Is rounding direction documented?
- Is rounding direction consistent with how the protocol uses the result?
- Can rounding errors accumulate across many operations?

---

## Step 5: Shared Object Dependencies

If the protocol uses shared objects from external packages:

| External Shared Object | Package | Our Functions That Access It | What We Read/Write | Behavior Change If Package Upgrades? |
|-----------------------|---------|----------------------------|-------------------|--------------------------------------|
| {oracle_obj} | {oracle_pkg} | {our_module::read_price} | READ price field | YES -- oracle upgrade could change price format |
| {dex_pool} | {dex_pkg} | {our_module::swap} | WRITE (swap) | YES -- DEX upgrade could change swap logic |

**Are we validating shared object state after external calls?**
- After reading price from external oracle shared object: do we validate freshness? bounds? format?
- After calling external DEX swap: do we validate received amount? slippage?
- If external package upgrades and changes shared object behavior, our code reads different data without any change on our side.

**Key risk**: External package with `compatible` upgrade policy can change function implementations. Our calls to those functions produce different results after upgrade, with no code change or compilation on our side.

---

## Step 6: Interface Compatibility

Could new abort conditions be added in dependency upgrades?

| Dependency Function | Current Abort Conditions | Possible New Abort Conditions | Impact on Our Protocol |
|-------------------|------------------------|-----------------------------|----------------------|
| {dep::func} | {list current aborts} | {what upgrades could add} | {describe: e.g., our transaction aborts unexpectedly} |

**Check**:
- If a dependency function currently never aborts but an upgrade adds an abort condition -> our protocol's transactions may start failing
- If a dependency function changes its return value semantics (e.g., rounding direction changes) -> our calculations become incorrect
- If a dependency adds new type constraints -> our generic calls may no longer compile on next recompile

---

## Key Questions (Must Answer All)

1. **Pinning**: Are all third-party dependencies pinned to specific git revisions?
2. **Critical functions**: For each math/utility function from a dependency, does it handle edge cases correctly?
3. **Bit shifts**: Are ALL bit shift operations in math libraries bounds-checked? (Cetus precedent)
4. **Upgrade risk**: Can any dependency change behavior without the protocol team's knowledge?
5. **Shared objects**: If we use shared objects from external packages, can their behavior change via upgrade?
6. **Transitive**: Are there transitive dependencies, and are they audited?

---

## Common False Positives

1. **Framework dependencies**: `sui::*` and `std::*` are validator-controlled and well-audited. Findings about framework functions are rarely valid unless version-specific.
2. **Pinned and immutable**: Dependency pinned to specific rev AND on-chain package is immutable -> no upgrade risk.
3. **Unused imports**: Dependency imported but no functions actually called -> no runtime risk.
4. **Well-known libraries**: Widely-used and audited libraries with specific rev pinning -> lower risk, but STILL check edge cases for specific functions used.

---

## Output Schema

```markdown
## Finding [DEP-N]: Title

**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: check1,2,3,4,5,6 | skip(reason) | uncertain
**Rules Applied**: [R1:___, R4:___, R8:___, R10:___]
**Severity**: Critical/High/Medium/Low/Info
**Location**: Move.toml or sources/{module}.move:LineN (where dep function is called)

**Dependency**: {dependency_name}
**Function**: {specific function if applicable}
**Issue Type**: UNPINNED_VERSION / ARITHMETIC_UNSAFE / BIT_SHIFT_UNSAFE / EDGE_CASE_UNHANDLED / SPEC_MISMATCH / TRANSITIVE_RISK / UPGRADE_RISK / SHARED_OBJECT_DEP

**Description**: What is wrong
**Impact**: What can happen (incorrect calculation, overflow, unexpected abort, supply manipulation)
**Evidence**: Code showing the issue
**Recommendation**: How to fix (pin version, add validation, use alternative, wrap with checks)
```

---

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Dependency Inventory | YES | | All deps from Move.toml enumerated |
| 2. Package Immutability Check | YES | | Pinning and on-chain policy for each dep |
| 3. Transitive Dependency Risk | YES | | Full dependency tree mapped |
| 4. Math Library Audit | IF math/arithmetic deps exist | | **HIGH PRIORITY** -- Cetus precedent |
| 4a. Bit Shift Operation Audit | IF bit shifts in math deps | | Every shift bounds-checked |
| 4b. Overflow/Underflow Audit | IF math deps | | Checked vs unchecked arithmetic |
| 4c. Rounding and Precision | IF division in math deps | | Direction documented and consistent |
| 5. Shared Object Dependencies | IF external shared objects used | | Behavior change on upgrade |
| 6. Interface Compatibility | IF upgradeable deps | | New abort conditions, return value changes |

### Cross-Reference Markers

**After Step 2**: If any dependency unpinned -> immediate Informational/Low finding.

**After Step 4**: If math library has unchecked bit shifts -> cross-reference with BIT_SHIFT_SAFETY skill for protocol-level impact analysis.

**After Step 5**: If shared object dependencies from upgradeable packages -> cross-reference with PACKAGE_VERSION_SAFETY Step 3 and EXTERNAL_PRECONDITION_AUDIT Step 3b.

If any step skipped, document valid reason (N/A, no third-party deps, framework-only, no math functions used).

## references/sui/economic-design-audit.md

---
name: "economic-design-audit"
description: "Trigger Pattern MONETARY_PARAMETER flag (required) - Inject Into Breadth agents (merged via M4 hierarchy)"
---

# ECONOMIC_DESIGN_AUDIT Skill (Sui)

> **Trigger Pattern**: MONETARY_PARAMETER flag (required)
> **Inject Into**: Breadth agents (merged via M4 hierarchy)
> **Purpose**: Analyze economic design of monetary parameters in Sui Move protocols for boundary violations, invariant breaks, and fee formula errors

For every monetary parameter setter (rate, supply, mint, burn, emission, inflation,
peg, price cap/floor, fee, reward rate) in the protocol:

## 1. Parameter Boundary Analysis

Enumerate all admin-settable monetary parameters stored in shared config objects:

| Parameter | Setter Function | Required Cap | Min Value | Max Value | Enforced? | Impact at Min | Impact at Max |
|-----------|----------------|-------------|-----------|-----------|-----------|---------------|---------------|
| {param} | {module::set_param} | {AdminCap} | {min} | {max} | {YES/NO} | {trace impact} | {trace impact} |

For each parameter: substitute min and max into ALL consuming functions.
Tag: [BOUNDARY:param=val -> outcome]

**Sui-specific**: Parameters are typically stored in shared objects (e.g., `Config`, `Registry`, `Pool`). Check:
- Are bounds enforced in the setter function? (`assert!(value >= MIN && value <= MAX)`)
- Can the setter bypass bounds? (e.g., separate `force_set` function)
- Are bounds hardcoded or stored (and themselves admin-settable)?

**Move arithmetic model**: Move uses unsigned integers only (`u8`, `u64`, `u128`, `u256`). No native BPS type.
- `u64` max: 18,446,744,073,709,551,615 (~1.8e19). SUI has 9 decimals, so max `u64` represents ~18.4 billion SUI.
- Overflow: Move aborts on arithmetic overflow/underflow by default (no silent wrapping). An overflow in a fee calculation aborts the entire transaction.
- Division by zero: Move aborts. Check all divisions where divisor is a parameter or derived from state.
- `amount * fee_bps` where amount is large: e.g., 1e18 * 10000 = 1e22 which OVERFLOWS u64. Check for `u128` intermediate casts.

## 2. Economic Invariant Identification

List all economic invariants the protocol must maintain:

| Invariant | Parameters Involved | Can Admin Break It? | Functions That Assume It |
|-----------|-------------------|--------------------|-----------------------|
| total_supply == sum(user_balances) | supply, balances | {YES/NO via mint/burn cap} | {list} |
| fee_rate <= MAX_FEE | fee_rate, MAX_FEE | {YES/NO} | {list} |
| collateral_value >= debt_value | collateral_ratio, prices | {YES/NO via param change} | {list} |

For each setter: can changing this parameter break an invariant that user-facing
functions depend on? If yes -> finding.

**Sui invariant patterns**:
- `TreasuryCap<T>` controls minting -- does unlimited minting break a peg or backing invariant?
- Shared pool balances must satisfy: `balance::value(&pool.token_a) * balance::value(&pool.token_b) >= k` (AMM invariant)
- Vault invariant: `total_shares * price_per_share <= total_assets` (no unbacked shares)

## 3. Rate/Supply Interaction Matrix

For protocols with multiple monetary parameters that interact:

| Parameter A | Parameter B | Interaction | Can A*B Produce Extreme Output? |
|-------------|-------------|-------------|--------------------------------|
| {reward_rate} | {total_supply} | reward_per_token = rate / supply | YES if supply -> 0 while rate > 0 |
| {fee_rate_A} | {fee_rate_B} | compound fee = A then B | YES if both at max -> excessive total fee |
| {mint_cap} | {burn_rate} | net supply = minted - burned | YES if mint >> burn -> inflation spiral |

Check: can two independently-valid parameter settings combine to create an
extreme or invalid economic state? (Rule 14 constraint coherence)

**Sui-specific interactions**:
- `TreasuryCap` mint + admin fee rate: can mint + fee combine to extract more than pool holds?
- Epoch-based emission + stake/unstake delay: can users game emission timing around epoch boundaries?

## 4. Fee Formula Verification at Normal Values

For every fee-related computation (fee calculation, fee deduction, fee distribution):

### 4a. Concrete Example Computation
Pick 3 representative fee rates (e.g., 1% = 100 BPS, 5% = 500 BPS, 10% = 1000 BPS) and trace through the actual code formula:

| Fee Param | Value | Formula | Input Amount | Expected Output | Actual Output | Match? |
|-----------|-------|---------|-------------|----------------|---------------|--------|
| {fee_bps} | 100 | {code formula} | 1_000_000_000 | {expected} | {computed} | YES/NO |
| {fee_bps} | 500 | {code formula} | 1_000_000_000 | {expected} | {computed} | YES/NO |
| {fee_bps} | 1000 | {code formula} | 1_000_000_000 | {expected} | {computed} | YES/NO |

Tag: `[BOUNDARY:fee_bps={val} -> effective_rate={computed_rate}]`

**Red flags**:
- Gross-up formulas: `amount * MAX / (MAX - fee)` charges effective rate of `fee/(MAX-fee)`, not `fee/MAX`. At 5% this is 5.26%, not 5%. Document whether this is intentional.
- Fee-on-fee: Does fee A's output feed into fee B's input? If so, the combined effective rate is not simply A + B.
- Rounding direction: In Sui Move, integer division truncates (rounds toward zero). Does this favor the protocol or the user? For fee deductions, `amount * fee / MAX` rounds down (user-favorable). Is `(amount * fee + MAX - 1) / MAX` used for protocol-favorable rounding?
- Precision loss: With `u64` math, do intermediate products overflow? Sui Move aborts on overflow -- is `u128` used for intermediate calculations? Check for `(amount as u128) * (fee as u128) / (MAX as u128)` patterns.
- **Gas budget constraint**: Sui transaction gas budget cap is 50 SUI (~50 billion MIST). If fee computation involves iteration, dynamic field traversal, or complex math, can gas exhaustion prevent fee collection or cause user transactions to fail? Check: are there unbounded loops in fee distribution paths?

### 4b. Fee Interaction Matrix
For protocols with multiple fee types:

| Fee A | Fee B | A Output Feeds B Input? | Combined Effective Rate | Independent Rate Sum | Discrepancy? |
|-------|-------|------------------------|------------------------|---------------------|-------------|

### 4c. Fee Impact on Share Price
If the protocol uses share-based accounting (vaults, LP tokens):
- After fee deduction: does the share price change?
- Does the fee mechanism create a spread between deposit and immediate withdrawal?
- Is the spread documented and within reasonable bounds?

### 4d. Fee-Base Consistency
For every fee computation, trace the base amount (the value the fee is computed on) through ALL subsequent code paths:

| Fee Site | Base Amount Variable | Modified After Fee? | Modified How | Fee Recomputed? | Overcharge? |
|----------|---------------------|--------------------:|-------------|-----------------|-------------|

**Methodology**:
- Identify the variable used as fee base (e.g., `amount`, `deposit_amount`)
- Trace that variable FORWARD from the fee computation to the end of the function
- If the variable is reduced (capped, downscaled, adjusted to remaining capacity) AFTER the fee was computed -> the fee was charged on a larger base than what was actually used
- **Concrete test**: If `fee = amount * fee_rate / MAX`, then `amount` is reduced to `leftover` (e.g., remaining allocation), the user paid `fee` on `amount` but only `leftover` was processed -- overcharge of `fee * (1 - leftover/amount)`

## 5. Emission/Inflation Sustainability

For protocols with emission/inflation/rebase mechanics:

| Emission Param | Max Rate | Over 1 Day | Over 1 Week | Over 1 Year | Sustainable? |
|---------------|---------|-----------|------------|------------|-------------|
| {reward_rate} | {max} | {computed} | {computed} | {computed} | {analysis} |

- What is the maximum emission rate over 1 day / 1 week / 1 year?
- Can emissions exceed the protocol's capacity to back them?
- Is there a supply cap enforced by `TreasuryCap` or explicit checks? Can it be bypassed by parameter changes?
- **Epoch-based emissions**: If rewards are distributed per epoch (~24h on Sui), can reward pool be drained faster than replenished?

## Finding Template

```markdown
**ID**: [ED-N]
**Severity**: [based on fund impact and parameter reachability]
**Step Execution**: check1,2,3,4,5 | x(reasons) | ?(uncertain)
**Rules Applied**: [R10:check, R14:check]
**Location**: module::function:LineN
**Title**: [Parameter/invariant issue] in [function] enables [impact]
**Description**: [Specific economic design issue with parameter trace]
**Impact**: [Quantified impact at boundary values]
```

## Instantiation Parameters
```
{CONTRACTS}           -- Move modules to analyze
{CONFIG_OBJECTS}      -- Shared config/registry objects
{MONETARY_PARAMS}     -- Admin-settable monetary parameters
{FEE_FUNCTIONS}       -- Functions computing or deducting fees
{INVARIANTS}          -- Known economic invariants
{EMISSION_PARAMS}     -- Emission/inflation parameters
{CAP_TYPES}           -- Capability types required for parameter changes
```

## Output Schema
| Field | Required | Description |
|-------|----------|-------------|
| parameter_boundaries | yes | All monetary params with min/max analysis |
| invariants | yes | Economic invariants and breakability |
| interaction_matrix | yes | Cross-parameter interactions |
| fee_verification | yes | Fee formula correctness at normal values |
| finding | yes | CONFIRMED / REFUTED / CONTESTED |
| evidence | yes | Code locations with line numbers |
| step_execution | yes | Status for each step |

---

## Step Execution Checklist (MANDATORY)

| Section | Required | Completed? |
|---------|----------|------------|
| 1. Parameter Boundary Analysis | YES | Y/N/? |
| 2. Economic Invariant Identification | YES | Y/N/? |
| 3. Rate/Supply Interaction Matrix | IF >1 monetary param | Y/N(N/A)/? |
| 4. Fee Formula Verification at Normal Values | IF fee parameters detected | Y/N(N/A)/? |
| 4d. Fee-Base Consistency | IF fee parameters detected | Y/N(N/A)/? |
| 5. Emission/Inflation Sustainability | IF emission/rebase detected | Y/N(N/A)/? |

## references/sui/external-precondition-audit.md

---
name: "external-precondition-audit"
description: "Trigger Pattern Any external package function call detected in program - Inject Into Breadth agents (merged via M5 hierarchy)"
---

# Skill: External Precondition Audit (Sui)

> **Trigger Pattern**: Any external package function call detected in program
> **Inject Into**: Breadth agents (merged via M5 hierarchy)
> **Finding prefix**: `[EPA-N]`
> **Rules referenced**: R1, R4, R8, R10
> **Constraint**: Interface-level inference only -- no production fetch required

```
use.*external|friend|public.*package|transfer::public_|dynamic_field|coin::from_balance|
clock::timestamp_ms|sui::pay|dex|swap|oracle|price_feed
```

For every external package the protocol calls:

## 1. Interface-Level Requirement Inference

From the imported module signatures, infer what the external package requires:

| External Function Called | Parameters Passed | Likely Preconditions (from signature) | Our Protocol Validates? | Package Immutable? |
|-------------------------|-------------------|--------------------------------------|------------------------|--------------------|

**Inference method**: Read the function signature, type constraints, and any doc comments. Example: `pool::swap<A, B>(pool: &mut Pool<A, B>, coin_in: Coin<A>, ...)` -> infer that `pool` must be the correct pool for `A/B` pair, `coin_in` must have sufficient balance, and return `Coin<B>` may have zero value (slippage).

**Package immutability check** (CRITICAL Sui-specific):
- Is the external package immutable (`UpgradeCap` destroyed)?
- If upgradeable: who holds the `UpgradeCap`? What upgrade policy (compatible, additive, dependency-only)?
- If upgradeable with `compatible` policy: the external package can change function behavior arbitrarily. Apply Rule 4 (adversarial assumption) -- treat the external package as potentially malicious after upgrade.
- If immutable: behavior is fixed, trust boundary is clear.

## 2. Return Value Consumption

| External Call | Return Type | How Protocol Uses Return | Failure Mode if Return Unexpected |
|--------------|-------------|-------------------------|----------------------------------|

For each return value:
- What happens if it returns a `Coin<T>` with zero balance? (division by zero, incorrect share calculation)
- What happens if it returns a `Coin<T>` with less value than expected? (slippage not checked)
- What happens if the call aborts? (entire PTB aborts -- can this be used for griefing?)
- **Hot potato returns**: If the external call returns a hot potato (zero-ability struct), is the consuming function always reachable in the same PTB? If not, the PTB always aborts.

**Sui-specific**: External package calls within a PTB share the same abort scope. If any external call aborts, the entire PTB reverts. Model: can an attacker cause an external call to abort to grief a user's multi-step PTB?

## 3. State Dependency Mapping

| Protocol State | Depends on External Shared Object | External State Can Change Between Epochs/Txns? |
|---------------|----------------------------------|------------------------------------------------|

For each dependency: model what happens when the external shared object state changes between our protocol's transactions.

**Sui-specific concerns**:
- Shared objects are ordered by consensus. Two transactions touching the same shared object are serialized. But transactions touching DIFFERENT shared objects can execute concurrently.
- If our protocol reads shared object A (external) and then writes shared object B (ours), another transaction can modify A between our read and our next access.
- **Cross-epoch state**: External shared objects may have epoch-dependent behavior (e.g., staking pools that update per epoch). Is our protocol aware of epoch boundaries?
- **Package upgrade state change**: If the external package upgrades, shared objects created by the old version may behave differently when accessed by functions from the new version. Does our protocol pin to a specific package version?

### 3b. Package Upgrade Risk Assessment

For each external package dependency:

| External Package | UpgradeCap Status | Upgrade Policy | Impact if Upgraded | Our Protocol's Mitigation |
|------------------|-------------------|---------------|-------------------|--------------------------|
| {package} | {destroyed (immutable) / held by {who}} | {compatible / additive / dep_only / immutable} | {behavior change risk} | {version pin / none} |

**Check**:
- Does the external package use `sui::package::UpgradeCap`? If so:
  - Who holds the `UpgradeCap`? (single admin, multisig, destroyed for immutability)
  - What upgrade policy is set? (`compatible` = can change anything, `additive` = can add but not change, `dep_only` = only dependency updates, `immutable` = frozen forever)
  - Can an upgrade change the behavior of functions our protocol depends on?
- Does our protocol pin to a specific package version, or does it follow upgrades automatically?
- If the external package upgrades with `compatible` policy: shared objects created by the old version may behave differently when accessed by the new version's functions. Our protocol may call into changed behavior without any code change on our side.

## Finding Template

```markdown
**ID**: [EPA-N]
**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: (see checklist below)
**Rules Applied**: [R1:___, R4:___, R8:___, R10:___]
**Severity**: Critical/High/Medium/Low/Info
**Location**: sources/{module}.move:LineN
**Title**: {missing external validation / unexpected return / state dependency}
**Description**: {specific issue with code reference}
**Impact**: {what attacker can achieve via the external package weakness}
```

---

## Step Execution Checklist (MANDATORY)

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Interface-Level Requirement Inference | YES | | Includes package immutability check |
| 2. Return Value Consumption | YES | | Hot potato return paths checked |
| 3. State Dependency Mapping | YES | | Cross-epoch + package upgrade state |
| 3b. Package Upgrade Risk | YES | | UpgradeCap holder + upgrade policy |

If any step skipped, document valid reason (N/A, no external packages, framework-only deps).

## references/sui/flash-loan-interaction.md

---
name: "flash-loan-interaction"
description: "Trigger Pattern FLASH_LOAN flag (required) or BALANCE_DEPENDENT flag (optional complement) - Inject Into Breadth agents, depth-token-flow, depth-edge-case"
---

# FLASH_LOAN_INTERACTION Skill (Sui)

> **Trigger Pattern**: FLASH_LOAN flag (required) or BALANCE_DEPENDENT flag (optional complement)
> **Inject Into**: Breadth agents, depth-token-flow, depth-edge-case
> **Purpose**: Analyze flash loan attack vectors in Sui Move protocols, focusing on hot potato receipt patterns and PTB-based atomic composition

For every flash-loan-accessible state variable or precondition in the protocol:

**STEP PRIORITY**: Steps 5 (Defense Audit) and 5b (Defense Parity) are where HIGH/CRITICAL severity findings most commonly hide. Do NOT rush these steps. If constrained, skip conditional sections (0c, 4) before skipping 5, 5b, or 3d.

## 0. External Flash Susceptibility Check

Before analyzing the protocol's OWN flash loan paths, check whether external protocols the contract interacts with are susceptible to third-party flash manipulation.

### 0a: External Interaction Inventory

| External Protocol | Interaction Type | State Read by Our Protocol | Can 3rd Party Flash-Manipulate That State? |
|-------------------|-----------------|---------------------------|-------------------------------------------|
| {DEX/pool/vault} | {swap/deposit/query} | {reserves, price, balance} | {YES if spot state / NO if TWAP or time-weighted} |

**Sui-specific**: Check interactions with known flash loan providers on the target chain (e.g., CLMM DEXs, lending protocols, orderbook DEXs). Each may provide flash loan functionality via hot potato receipts within PTBs.

### 0b: Third-Party Flash Attack Modeling

For each external state marked YES in 0a, model:
1. **Before**: Protocol reads external state X (e.g., pool reserves, spot price)
2. **Flash manipulate**: Attacker flash-borrows from {source} and trades on the external protocol to move state X
3. **Victim call**: Attacker calls OUR protocol function that reads manipulated state X -- all within the SAME PTB
4. **Restore**: Attacker reverses the external manipulation
5. **Impact**: What did the attacker gain from our protocol acting on manipulated state?

**Key question**: Does our protocol use **spot state** (manipulable) or **time-weighted state** (resistant)?

<!-- LOAD_IF: DEX_INTERACTION -->
### 0c: DEX Price Manipulation Cost Estimation

For each external DEX/pool whose spot state is read by the protocol, estimate manipulation cost:

| Pool | Liquidity (USD) | Target Price Change | Est. Trade Size | Slippage Cost | Protocol Extractable Value | Profitable? |
|------|----------------|--------------------:|----------------|--------------|---------------------------|-------------|
| {pool} | {TVL} | {%} | {USD} | {USD} | {USD} | {YES/NO} |

**Sui DEX types**:
- **CLOB (DeepBook)**: Manipulation via limit order placement + market orders. Cost depends on order book depth.
- **CLMM DEXs**: Concentrated liquidity -- manipulation cost depends on liquidity in active tick range, not total TVL.
- **AMM (other)**: Standard constant-product -- `price_impact = trade_size / (reserve + trade_size)`.
<!-- END_LOAD_IF: DEX_INTERACTION -->

## 1. Flash-Loan-Accessible State Inventory

Enumerate ALL protocol state that can be manipulated within a single PTB via flash-borrowed capital:

| State Variable / Query | Location | Read By | Write Path | Flash-Accessible? | Manipulation Cost |
|------------------------|----------|---------|------------|-------------------|-------------------|
| `balance::value(&pool.balance)` | {module} | {functions} | deposit/withdraw | YES | Deposit amount |
| `pool.total_supply` | {module} | {functions} | mint/burn | YES if permissionless | Deposit amount |
| DEX pool reserves | {external} | {functions} | Swap | YES | Slippage cost |
| Oracle spot price | {external} | {functions} | Trade on source | YES | Market depth |
| Threshold/quorum state | {module} | {functions} | Deposit/stake | YES | Threshold amount |

**Sui-specific flash loan mechanics**:
- Flash loans on Sui use the **hot potato pattern**: a `FlashLoanReceipt` struct with NO abilities (no `key`, `store`, `copy`, or `drop`). It MUST be consumed by the repay function in the same PTB.
- PTBs allow up to **1024 commands** -- an attacker can compose: borrow -> N manipulations -> exploit -> repay in a single atomic transaction.
- No callback mechanism needed -- PTB command sequencing handles atomicity.
- Flash loan sources: lending protocols (`flash_loan` / `repay_flash_loan` patterns), DEX flash swaps, flash mint mechanisms.

**For each YES entry**: trace all functions that READ this state and make decisions based on it.

**Rule 15 check**: For each balance/oracle/threshold/rate precondition, model the flash loan atomic sequence within a PTB.

## 2. Atomic Attack Sequence Modeling

For each flash-loan-accessible state identified in Step 1:

### Attack Template (PTB-Based)
```
PTB Commands:
  1. BORROW: Call flash_loan({amount}, {token}) on {source} -> receive Coin<T> + FlashLoanReceipt
  2. MANIPULATE: {action} to change {state_variable} from {value_before} to {value_after}
  3. CALL: Invoke {target_function} on our protocol which reads manipulated state
  4. EXTRACT: {what_is_gained} -- quantify: {amount}
  5. RESTORE: {action} to return state (if needed for repayment)
  6. REPAY: Call repay_flash_loan(receipt, coin) -- consumes hot potato receipt
  7. PROFIT: {extract - fee - gas} = {net_profit}
```

**Profitability gate**: If net_profit <= 0 for all realistic amounts -> document as NON-PROFITABLE but check Step 3 for multi-call chains.

**For each sequence, verify**:
- [ ] Can steps 2-5 execute within a single PTB (max 1024 commands)?
- [ ] Does any step abort under normal conditions?
- [ ] Is the manipulation detectable/preventable by the protocol?
- [ ] What is the minimum flash loan amount needed?
- [ ] Does the hot potato receipt enforce correct repayment (amount + fee)?

## 3. Cross-Function Flash Loan Chains

Model multi-call atomic sequences within a single PTB:

| PTB Cmd | Function Called | Shared Object State Before | State After | Enables Next Step? |
|---------|---------------|---------------------------|------------|-------------------|
| 1 | {function_A} | {state} | {state'} | YES -- changes {X} |
| 2 | {function_B} | {state'} | {state''} | YES -- enables {Y} |
| N | {function_N} | {state^N} | {final} | EXTRACT profit |

**Key question**: Can calling function A then function B in the same PTB produce a state that neither function alone could create?

**Common Sui multi-call patterns**:
- Deposit -> manipulate share price -> withdraw (sandwich own deposit)
- Stake -> trigger reward calculation -> unstake (flash-stake rewards)
- Flash borrow -> inflate collateral value -> borrow against inflated collateral -> repay flash loan
- Deposit to inflate shares -> withdraw deflated shares
- Flash borrow -> manipulate oracle state -> liquidate others -> repay

### 3b. Flash-Loan-Enabled Debounce DoS
For each permissionless function with a cooldown/debounce stored in a shared object:
Can attacker flash-borrow -> call debounced function -> trigger cooldown, blocking legitimate callers?

| Function | Cooldown Scope | Shared Across Users? | Flash-Triggerable? | DoS Duration |
|----------|---------------|---------------------|-------------------|-------------|

**Sui-specific**: Cooldowns on Sui typically use `clock::timestamp_ms(clock)` comparisons stored in shared objects. If the cooldown timestamp is global (not per-user), a flash loan can trigger it for all users.

If cooldown is global/shared AND function is permissionless AND flash-triggerable -> FINDING (R2, minimum Medium).

### 3c. No-Op Resource Consumption
For each state-modifying function with a limited-use resource (cooldown, one-time flag, epoch-bound action):
Can it be called with parameters producing zero economic effect (amount=0, same-token swap, self-transfer) while consuming the resource?

| Function | Resource Consumed | No-Op Parameters | Resource Wasted? | Impact |
|----------|------------------|-----------------|-----------------|--------|

If a no-op call consumes a resource blocking legitimate use -> FINDING (R2, resource waste).

### 3d. External Flash x Debounce Cross-Reference (MANDATORY)

For EACH external protocol flagged as flash-susceptible in Section 0:

| External Protocol | Flash-Accessible Action | Debounce/Cooldown Affected (from 3b) | Combined Severity |
|-------------------|------------------------|--------------------------------------|-------------------|

Cross-reference: Can the external flash loan trigger ANY debounce/cooldown found in Step 3b?
If YES:
1. Is the debounce consumption **permanent** (no admin reset) or **temporary** (auto-expires)?
2. If permanent: is there ANY on-chain path to reset? (admin cap function, epoch reset, time-based expiry)
3. Combined finding inherits the HIGHER severity of the two individual findings
4. Tag: `[TRACE:flash({external}) -> call({debounce_fn}) -> cooldown consumed -> {duration/permanent}]`

If no debounce functions exist from 3b: mark N/A and skip.

## 4. Hot Potato Receipt Integrity

For every flash loan implementation in the protocol (or external flash loan receipt consumed by the protocol):

### 4a. Receipt Struct Analysis

| Receipt Struct | Abilities | Can Be Constructed Outside Module? | Fields Validated on Repay? | Amount+Fee Enforced? |
|----------------|-----------|-----------------------------------|--------------------------|---------------------|
| {struct_name} | {none / key / store / etc.} | YES/NO | {list fields checked} | YES/NO |

**Critical checks**:
- Receipt struct MUST have zero abilities (no `key`, `store`, `copy`, `drop`). If it has `drop` -> borrower can discard receipt without repaying (free flash loan).
- Receipt struct MUST be defined in the lending module with no public constructor. If any public function returns a freshly created receipt -> attacker can fabricate receipts.
- Repayment function MUST validate returned `Coin<T>` value >= borrowed amount + fee. If it only checks receipt existence -> underpayment.

### 4b. Receipt Replay / Fabrication

- Can the receipt be split or partially consumed? (e.g., paying back in multiple calls)
- Can two receipts from different borrows be combined or swapped?
- Is the receipt tied to a specific pool/object ID? If not -> cross-pool receipt confusion.
- Can a receipt be constructed via `test_utils` or `test_scenario` in production? (should be test-only)

### 4c. PTB Receipt Composition

- Can multiple flash loan receipts from different pools coexist in the same PTB?
- If two receipts exist, can the repayment coins be swapped (pay Pool A's receipt with Pool B's funds)?
- Does the receipt encode the borrow pool's ID to prevent cross-pool repayment?

<!-- LOAD_IF: BALANCE_DEPENDENT -->
## 5. Flash Loan + Donation Compound Attacks

Combine flash loan capital with unsolicited token transfers:

| Donation Target | Flash Loan Action | Combined Effect | Profitable? |
|-----------------|-------------------|-----------------|-------------|
| Shared pool object balance | Deposit/withdraw | Rate manipulation | {YES/NO} |
| DEX pool reserves | Swap | Price oracle manipulation | {YES/NO} |
| Governance voting power | Vote/propose | Quorum manipulation | {YES/NO} |

**Sui-specific donation vectors**:
- `transfer::public_transfer(coin, @pool_address)` sends Coin<T> to a shared object's address -- but this creates a NEW owned object at that address, NOT added to the shared object's `Balance<T>`. However, some protocols use `dynamic_field` or accept arbitrary coins.
- Check: Does the protocol have a function that accepts arbitrary `Coin<T>` and adds to its `Balance<T>` without proper accounting? (e.g., a `donate()` or `top_up()` function)
- Check: Does the protocol read `balance::value()` of its own balance and use it for exchange rate calculation? If so, any path to increase the balance without minting shares is a donation attack vector.
<!-- END_LOAD_IF: BALANCE_DEPENDENT -->

## 6. Flash Loan Defense Audit

For each flash-loan-accessible attack path identified:

| Defense | Present? | Effective? | Bypass? |
|---------|----------|------------|---------|
| Hot potato receipt validation (amount + fee) | YES/NO | {analysis} | {if YES: how} |
| Same-epoch prevention (epoch comparison) | YES/NO | {analysis} | Multi-epoch possible? |
| TWAP instead of spot price | YES/NO | TWAP window length: {N} | Short TWAP vulnerable? |
| Minimum lock period / cooldown | YES/NO | Duration: {N epochs/seconds} | Bypass via partial? |
| Balance snapshot (before/after in same function) | YES/NO | {analysis} | {if YES: how} |
| Flash loan fee exceeds profit | YES/NO | Fee: {X}, max profit: {Y} | Fee < profit? |
| PTB command limit (1024) constrains attack | YES/NO | Commands needed: {N} | N < 1024? |

**Sui-specific defenses**:
- Hot potato pattern inherently prevents cross-transaction flash loans -- but does NOT prevent within-PTB manipulation.
- `tx_context::epoch()` checks prevent cross-epoch attacks but NOT same-PTB attacks.
- Shared object contention: high-contention shared objects may naturally serialize, but this is NOT a reliable defense.

## 6b. Defense Parity Audit (Cross-Module)

For each user-facing action that exists in multiple modules (stake, withdraw, claim, exit):

| Action | Module A | Flash Defense | Module B | Flash Defense | Parity? |
|--------|---------|---------------|---------|---------------|---------|
| {action} | {module} | {defense list} | {module} | {defense list} | {GAP if different} |

**Key question**: If ModuleA::stake() has a cooldown that prevents flash-stake-claim-withdraw, but ModuleB::stake() has NO cooldown for the same economic action -- can an attacker use ModuleB as the undefended path to extract the same value?

For each GAP found:
1. Can the undefended module be used to achieve the same economic outcome?
2. Does the defended module's protection become meaningless if the undefended path exists?
3. Is the defense difference intentional (documented) or accidental?

## Finding Template

```markdown
**ID**: [FL-N]
**Severity**: [based on profitability and fund impact]
**Step Execution**: check0,1,2,3,4,5,6 | x(reasons) | ?(uncertain)
**Rules Applied**: [R2:check, R4:check, R10:check, R15:check]
**Location**: module::function:LineN
**Title**: Flash loan enables [manipulation] via [mechanism] within PTB
**Description**: [Full atomic PTB attack sequence with amounts]
**Impact**: [Quantified profit/loss with realistic flash loan amounts]
```

## Instantiation Parameters
```
{CONTRACTS}           -- Move modules to analyze
{FLASH_SOURCES}       -- Flash loan providers identified during recon (lending protocols, DEXs, etc.)
{SHARED_OBJECTS}      -- Shared objects with flash-accessible state
{BALANCE_VARS}        -- Balance<T> fields readable by protocol
{DEX_POOLS}           -- External DEX pools interacted with
{PTB_COMPOSABLE}      -- Functions composable within PTBs
```

## Output Schema
| Field | Required | Description |
|-------|----------|-------------|
| flash_state_inventory | yes | All flash-loan-accessible state |
| atomic_sequences | yes | PTB-based attack sequences modeled |
| cross_function_chains | yes | Multi-call chains within PTBs |
| defense_audit | yes | Defenses present and effectiveness |
| defense_parity | yes | Cross-module defense consistency |
| finding | yes | CONFIRMED / REFUTED / CONTESTED |
| evidence | yes | Code locations with line numbers |
| step_execution | yes | Status for each step |

---

## Step Execution Checklist (MANDATORY)

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 0. External Flash Susceptibility Check | YES | check/x/? | For each external protocol interaction |
| 1. Flash-Loan-Accessible State Inventory | YES | check/x/? | |
| 2. Atomic Attack Sequence Modeling | YES | check/x/? | For each accessible state |
| 3. Cross-Function Flash Loan Chains | YES | check/x/? | |
| 3b. Flash-Loan-Enabled Debounce DoS | YES | check/x/? | Shared cooldown functions |
| 3c. No-Op Resource Consumption | YES | check/x/? | Zero-effect calls consuming resources |
| 3d. External Flash x Debounce Cross-Ref | YES | check/x/? | Cross-reference 0 x 3b |
| 4. Hot Potato Receipt Integrity | YES | check/x/? | Receipt abilities + repayment validation |
| 5. Flash Loan + Donation Compounds | IF BALANCE_DEPENDENT | check/x(N/A)/? | |
| 6. Flash Loan Defense Audit | YES | check/x/? | For each attack path |
| 6b. Defense Parity Audit | YES | check/x/? | For each action in multiple modules |

## references/sui/fork-ancestry.md

---
name: "fork-ancestry"
description: "Trigger Pattern Always (run during recon TASK 0, not breadth) - Inject Into Recon agent only (meta_buffer.md enrichment)"
---

# FORK_ANCESTRY Skill (Sui)

> **Trigger Pattern**: Always (run during recon TASK 0, not breadth)
> **Inject Into**: Recon agent only (meta_buffer.md enrichment)
> **Finding prefix**: `[FA-N]`
> **Purpose**: Detect known parent Sui packages and inherit their historical vulnerability patterns.

---

## 1. Detect Fork Indicators

Grep the codebase for known parent Sui package signatures:

| Parent Project | Detection Patterns | Common Forks |
|---------------|-------------------|--------------|
| Cetus | `cetus\|clmm\|tick\|concentrated_liquidity\|cetus_clmm\|tick_math\|sqrt_price_math\|CetusPool` | Concentrated liquidity forks |
| Suilend | `suilend\|lending_market\|reserve\|obligation\|refresh_reserve\|LendingMarket\|ObligationKey` | Lending protocol forks |
| NAVI | `navi\|navi_protocol\|lending\|pool_manager\|incentive\|StoragePool\|navi_lending` | Lending protocol forks |
| Scallop | `scallop\|s_coin\|market\|obligation\|borrow_dynamics\|ScallopMarket\|sCoin` | Lending protocol forks |
| Turbos | `turbos\|pool_factory\|position_manager\|turbos_clmm\|TurbosPool\|TurbosPosition` | Concentrated liquidity forks |
| DeepBook | `deepbook\|clob\|order_book\|custodian\|deep_book\|DeepBookPool\|BalanceManager` | Order book DEX forks |
| Aftermath | `aftermath\|af_lp\|pool_registry\|amm_v2\|AftermathPool\|StakedSui` | AMM / liquid staking forks |
| Bucket | `bucket\|bucket_protocol\|tank\|well\|fountain\|BucketProtocol\|BUCK` | Stablecoin / CDP forks |
| Kriya | `kriya\|kriya_dex\|spot_dex\|clmm\|KriyaPool\|KriyaPosition` | DEX forks |
| FlowX | `flowx\|flowx_clmm\|router\|pair_v2\|FlowXPool\|FlowXRouter` | DEX forks |
| Sui System Staking | `staking_pool\|validator\|sui_system\|delegation\|StakedSui\|StakingPool\|ValidatorCap` | Liquid staking / validator forks |

**Also check**:
- `Move.toml` dependencies for parent package addresses or names (e.g., `cetus_clmm = "0x..."`, `deepbook = { addr = "0x..." }`)
- Import paths in Move source: `use cetus_clmm::`, `use deepbook::`, `use suilend::`, etc.
- Struct names and function signatures matching known parent interfaces
- Published package addresses in dependency declarations (known mainnet addresses of parent protocols)

**Output**: List of detected parents with confidence level:
- **HIGH**: 3+ unique patterns matched, OR parent package in Move.toml dependencies
- **MEDIUM**: 2 patterns matched
- **LOW**: 1 pattern matched (may be coincidental naming)

---

## 2. Query Known Parent Issues

For each detected parent (confidence MEDIUM or HIGH):

### 2a. Solodit Search (two queries, run in parallel)
```
// Query 1: Known high-quality issues
search_solodit_live(
  keywords="{parent_name} sui move",
  impact=["HIGH", "CRITICAL"],
  language="Move",
  quality_score=3,
  sort_by="Quality",
  max_results=15
)
// Query 2: Fork-specific divergence issues
search_solodit_live(
  keywords="{parent_name} fork modified sui object",
  impact=["HIGH", "MEDIUM"],
  language="Move",
  sort_by="Rarity",
  max_results=10
)
```

### 2b. Tavily Search
```
tavily_search(query="{parent_name} sui move vulnerability exploit audit finding 2024 2025 2026")
```

### 2c. Known Issue Catalog

Compile results into:

| Parent | Known Issue | Severity | Root Cause | Solodit Ref | Applicable to Fork? |
|--------|-----------|----------|------------|-------------|---------------------|
| {parent} | {issue title} | {severity} | {brief root cause} | {link/ID} | YES / NO / CHECK |

**Applicability criteria**:
- **YES**: Fork retains the vulnerable code path unchanged
- **NO**: Fork modified the vulnerable code path (document what changed)
- **CHECK**: Cannot determine without deeper analysis (flag for breadth agent)

### 2d. Hardcoded Known-Issue Floor (Web Search Fallback)

If Solodit AND Tavily BOTH fail, use this minimum catalog -- check EACH applicable parent:

| Parent | Critical Known Issue | Root Cause | Search Keywords |
|--------|---------------------|------------|-----------------|
| CLMM DEX | Tick boundary crossing precision loss + liquidity accounting desync | sqrt_price calculation at tick boundaries, Position NFT state vs pool liquidity mismatch | `clmm tick precision sqrt_price` |
| Lending protocol (obligation-based) | Obligation refresh staleness + liquidation racing on shared objects | Reserve refresh not enforced before obligation health check, concurrent tx ordering | `lending obligation refresh stale liquidation shared object` |
| Lending protocol (pool-based) | Pool balance desync via flash loan deposit/withdraw + incentive calculation overflow | Balance tracking diverges from actual Coin balance, large TVL causes incentive arithmetic overflow | `lending balance flash loan pool desync` |
| Lending protocol (receipt-token) | Receipt token exchange rate manipulation via first depositor + borrow dynamics staleness | Empty market rounding in receipt token minting, stale interest rate applied across epochs | `lending receipt token exchange rate first deposit borrow dynamics` |
| Orderbook DEX | Order matching priority manipulation + balance manager accounting edge cases | Self-trading for priority manipulation, dust amounts in partial fills | `orderbook order priority self-trade balance dust` |
| CDP/stablecoin protocol | Reward distribution fairness + overflow at extreme collateral ratios | Discrete epoch distribution timing, arithmetic overflow in collateral ratio calculation | `cdp reward epoch collateral overflow` |
| Sui System Staking | Validator list manipulation via stake deposit ordering + reward fee timing | Stake account priority ordering in validator selection, reward distribution during epoch boundary | `sui staking validator reward epoch boundary` |
| Aftermath/AMM | LP share price manipulation via donation to pool + StakedSui exchange rate lag | Direct Coin transfer to pool object inflates share price, staking rewards not reflected immediately | `aftermath pool share price donation stakedSui` |

---

## 3. Divergence Analysis

For each detected parent:

### 3a. Identify What Changed

Compare fork vs parent in security-critical paths:

| Component | Parent Behavior | Fork Behavior | Security Impact |
|-----------|----------------|---------------|-----------------|
| {component} | {original} | {modified or SAME} | {new risk or NONE} |

**Sui-specific divergence focus areas** (ordered by criticality):

#### Object Ownership Model Changes (HIGHEST PRIORITY)
- Did the fork change any object from OWNED to SHARED or vice versa? Ownership model changes fundamentally alter the access control surface.
- Did the fork add or remove `store` ability from objects? Adding `store` = anyone can transfer; removing `store` = module-controlled transfer only.
- Did the fork change shared object access control patterns (different capability checks, different admin verification)?
- **Critical**: Shared object without proper access control = anyone can mutate protocol state.

#### Capability and Admin Pattern Changes
- Did the fork change which capabilities gate admin operations (different Cap objects, different verification logic)?
- Did the fork add `store` to capability objects that the parent kept module-restricted? This allows capability transfer, potentially weakening admin control.
- Did the fork introduce new admin functions without corresponding capability checks?
- **Critical**: Capability objects with `store` can be transferred to arbitrary addresses, including contracts that auto-execute.

#### Balance and Coin Handling Changes
- Did the fork modify how `Balance<T>` or `Coin<T>` objects are split, joined, or transferred?
- Are there new code paths where Balance could be created (via `balance::zero()` then never destroyed) or destroyed (via unmatched `balance::destroy_zero()`)?
- Did the fork add support for additional coin types without updating all code paths?
- **Critical**: Balance accounting mismatches between protocol state and actual Coin holdings.

#### Dynamic Field Schema Changes
- Did the fork change dynamic field key types or naming conventions?
- Are there new dynamic field additions without corresponding removal logic?
- Did the fork change which objects have dynamic fields attached?

#### Other Divergence Areas
- Modified mathematical formulas (fee calculations, exchange rates, reward distribution)
- Changed access control (added/removed capabilities, modified authority checks)
- Removed safety checks (assertions removed, constraints removed)
- Changed struct layouts (fields reordered, types changed, new fields added)
- Added/removed public functions (new attack surface or missing safety functions)
- Changed event emissions (may affect off-chain monitoring and indexers)

### 3b. New Attack Surface from Divergence

For each modification:
- Does the change introduce a NEW vulnerability not in the parent?
- Does the change REMOVE a parent fix/mitigation?
- Does the change create an INCONSISTENCY with parent's invariants?
- **Does the change break assumptions that other unchanged code relies on?** (e.g., parent assumes PoolConfig is always shared; fork sometimes wraps it inside another object)

---

## 4. Output to meta_buffer.md

Append to `{SCRATCHPAD}/meta_buffer.md`:

```markdown
## Fork Ancestry Analysis

### Detected Parents
| Parent | Confidence | Patterns Found | Move.toml Dependency? |
|--------|-----------|---------------|----------------------|

### Inherited Vulnerabilities to Verify
| # | Parent Issue | Severity | Location in Fork | Status |
|---|-------------|----------|------------------|--------|
| 1 | {issue} | {severity} | {fork location: module::function} | CHECK / VERIFIED_SAFE / VULNERABLE |

### Fork Divergences (Security-Critical)
| # | Component | Change Type | Change Description | New Risk? |
|---|-----------|------------|-------------------|-----------|
| 1 | {component} | OWNERSHIP_MODEL / CAPABILITY / BALANCE / DYNAMIC_FIELD / OTHER | {what changed} | YES/NO/CHECK |

### Questions for Breadth Agents
1. {derived from inherited vulnerabilities}
2. {derived from divergence analysis}
3. {derived from ownership model changes}
```

---

## Step Execution Checklist (MANDATORY)

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Detect Fork Indicators | YES | Y/N/? | Check Move.toml deps + source patterns |
| 2. Query Known Parent Issues | IF parent detected | Y/N(no parent)/? | |
| 2d. Hardcoded Known-Issue Floor | IF Solodit+Tavily both fail | Y/N(not needed)/? | |
| 3. Divergence Analysis | IF parent detected | Y/N(no parent)/? | |
| 3a. Object Ownership Model Changes | IF parent detected | Y/N(no parent)/? | Highest priority |
| 3a. Capability and Admin Pattern Changes | IF parent detected | Y/N(no parent)/? | |
| 3a. Balance and Coin Handling Changes | IF parent detected | Y/N(no parent)/? | |
| 3a. Dynamic Field Schema Changes | IF parent detected | Y/N(no parent)/? | |
| 4. Output to meta_buffer.md | YES | Y/N/? | |

### Cross-Reference Markers

**After Step 1**: If Move.toml shows specific parent package address dependencies, verify the addresses match known mainnet deployments (not test/devnet).

**After Step 3a (Ownership Model)**: Feed changed ownership models to OBJECT_OWNERSHIP skill for targeted re-analysis of affected objects.

**After Step 3a (Capability)**: Feed new/changed capabilities to SEMI_TRUSTED_ROLES skill for admin privilege analysis.

**After Step 3a (Balance)**: Feed changed balance handling to TOKEN_FLOW_TRACING skill for flow analysis.

## references/sui/migration-analysis.md

---
name: "migration-analysis"
description: "Trigger Pattern Package upgrades, version transitions, deprecated functions, object layout changes - Inject Into Breadth agents, depth-state-trace"
---

# Skill: Migration Analysis (Sui)

> **Trigger Pattern**: Package upgrades, version transitions, deprecated functions, object layout changes
> **Inject Into**: Breadth agents, depth-state-trace
> **Finding prefix**: `[MG-N]`
> **Rules referenced**: R4, R8, R9, R10, R13

```
upgrade|UpgradeCap|UpgradeTicket|version|v2|V2|deprecated|migrat|legacy|old_coin|new_coin|
compatible|additive|dependency_only|package::authorize_upgrade
```

Sui packages are immutable once published. "Upgrades" create new package versions at new addresses via the UpgradeCap mechanism. Old objects may reference old package versions, and mixed-version state is a primary Sui migration concern. Unlike EVM proxy upgrades where old code is replaced, Sui old package code remains callable forever.

---

## Step 1: Identify Token Transitions

Find all token/object migration patterns:
- Old `Coin<OldType>` -> New `Coin<NewType>` (token rebranding, coin type changes)
- Old object layouts -> New object layouts (fields added via upgrade)
- Deprecated functions still callable via old package version
- Shared objects created by V1 that must work with V2 functions

For each transition:

| Old Entity | New Entity | Migration Function | Bidirectional? | Entity Type |
|------------|-----------|-------------------|----------------|-------------|

**Sui-specific check**: Is this a true package upgrade (same package lineage via UpgradeCap) or a separate package deployment (new address, no lineage)? True upgrades maintain type compatibility; separate deployments create entirely new types -- `pkg_v1::module::Type` != `pkg_v2::module::Type`.

---

## Step 2: Check Interface Compatibility

For each function in the new package version interacting with existing objects:

1. What fields does the OLD object layout have?
2. What fields does the NEW version's functions expect?
3. Are new fields appended (compatible upgrade) or does the layout change (breaking)?
4. Does the struct definition change in a way that breaks `compatible` upgrade rules?

```move
// Example: V1 object layout
struct Pool has key, store {
    id: UID,
    balance: Balance<SUI>,
    fee_bps: u64,
}
// V2 cannot change this layout under `compatible` policy.
// New fields require a new struct or dynamic fields.
```

| Object Type | V1 Fields | V2 Fields | Compatible Upgrade? | Breaking Change? |
|-------------|-----------|-----------|--------------------|--------------------|

**Sui upgrade compatibility rules**:
- `compatible`: Can change function implementations, add new functions, add new types. CANNOT change existing struct layouts, remove public functions, or change function signatures.
- `additive`: Can only add new modules/functions. Cannot change existing code.
- `dependency_only`: Can only change dependency versions.
- `immutable`: No changes possible (`UpgradeCap` destroyed).

---

## Step 3: Trace Token Flow Paths

For each function that interacts with migrated tokens/objects:

1. **Entry point**: What object version does the user provide?
2. **Internal flow**: What version does the protocol logic expect?
3. **External call**: What version do external packages expect?
4. **Return value**: What version is returned?

| Function | User Provides | Protocol Expects | External Expects | Mismatch? |

### Step 3b: External Side Effect Compatibility

When migration changes token types or interaction patterns, check whether external package side effects produce tokens/objects the current logic handles:

| External Call | Pre-Migration Side Effect | Post-Migration Side Effect | Logic Handles Both? | Mismatch? |
|---------------|--------------------------|---------------------------|---------------------|-----------|

**Pattern**: Migration changes the primary coin type (e.g., V1 -> V2), but external packages still return the old type as rewards or receipts.

### Step 3c: Pre-Upgrade Object Inventory

Before analyzing stranded asset paths, inventory what shared objects and owned objects exist on-chain:

| Object Type | Ownership | How Created | Current Contents | Post-Upgrade Logic Handles? | Exit Path Post-Upgrade? |
|-------------|-----------|------------|-----------------|----------------------------|------------------------|
| {pool_obj} | Shared | `init()` | Balance<SUI> + config | YES/NO | {function or NONE} |
| {user_position} | Owned | `open_position()` | Balance + tracking | YES/NO | {function or NONE} |
| {legacy_receipt} | Owned | V1 `deposit()` | Receipt token | YES/NO | {function or NONE} |

**Sui-specific**: Unlike EVM where contract storage persists across upgrades, Sui objects are typed. If a new package is published (not a lineage upgrade), V1 objects CANNOT be read by new package functions because the types differ.

---

## Step 4: Stranded Asset Analysis (ENHANCED)

> **CRITICAL**: This step uses exhaustive methodology. Every sub-step is MANDATORY.

#### 4a. Asset Inventory by Version

| Asset/Object | V1 Entry Path | V2 Entry Path | V1 Exit Path | V2 Exit Path |
|--------------|---------------|---------------|--------------|--------------|
| {shared_pool} | `create_pool()` | `create_pool()` (same) | N/A (shared) | N/A (shared) |
| {user_receipt} | `deposit()` | `deposit_v2()` | `redeem()` | `redeem_v2()` |

**Rule**: If V1 Entry exists but V2 Exit doesn't handle V1-created objects -> potential stranding.

**Sui-specific**: Shared objects persist across compatible upgrades. For new package publications (not upgrades), the type is a DIFFERENT type even if structurally identical.

#### 4b. Cross-Version Path Matrix

| Object Version | State Condition | Available Exit Paths | Works? | Reason |
|----------------|----------------|---------------------|--------|--------|
| V1 user receipt | V2 package active | `redeem_v2()` with V1 receipt | Y/N | {struct type compatibility} |
| V1 shared pool | V2 functions called on it | V2 `withdraw()` | Y/N | {field access compatibility} |
| V1 owned object | New package published (not upgraded) | ANY V2 function | Y/N | {type mismatch} |

**STRANDING RULE**: If ALL exit paths = N for any object state -> **STRANDED ASSETS FINDING** (apply Rule 9: minimum MEDIUM)

#### 4c. Recovery Function Inventory

| Function | Who Can Call | What Objects Can Recover | Limitations |
|----------|------------|------------------------|-------------|
| `migrate_v1()` | Object owner | V1 owned objects | One-time per object |
| `admin_sweep()` | AdminCap holder | Shared object balances | Requires active AdminCap |

**Question**: Is there a recovery path for EVERY stranding scenario in 4b?

#### 4d. Worst-Case Scenarios (MANDATORY)

**Scenario 1: V1 Object + V2 Package (Compatible Upgrade)**
```
State: User holds Receipt object created by V1 package
Event: Package upgraded to V2 via compatible upgrade
Question: Can user call V2 redeem() with V1 Receipt?
Trace: [document type compatibility and field access]
Result: [SUCCESS/STRANDED + amount]
```

**Scenario 2: V1 Object + New Package (Non-Upgrade Publication)**
```
State: User holds Receipt<OldPackage::COIN> object
Event: Protocol publishes new package at new address
Question: Can user interact with new package using old object?
Trace: [OldPackage::Receipt != NewPackage::Receipt -- different types]
Result: [STRANDED unless explicit migration exists]
```

**Scenario 3: Old Package Bypass After Upgrade**
```
State: Protocol upgrades to V2 with new security checks
Event: Attacker calls V1 functions on shared objects
Question: Do V1 functions bypass V2 security checks?
Trace: [document old function code path]
Result: [SAFE/BYPASS POSSIBLE]
```

**Scenario 4: Dynamic Field Key Type Mismatch**
```
State: Objects have dynamic fields with V1 key types
Event: V2 changes key type or field structure
Question: Can V2 code read/remove V1-era dynamic fields?
Trace: [document dynamic field access path]
Result: [SUCCESS/STRANDED + orphaned dynamic fields]
```

#### 4e. Step 4 Completion Checklist
- [ ] 4a: ALL objects inventoried with entry/exit paths per version
- [ ] 4b: Cross-version path matrix completed for all state combinations
- [ ] 4c: Recovery functions enumerated with limitations
- [ ] 4d: All four worst-case scenarios modeled with code traces
- [ ] For EVERY stranding possibility: recovery path exists OR finding created

### Step 4f: User-Blocks-Admin Scenarios

| Admin/Migration Function | Precondition Required | User Action That Blocks It | Timing Window | Severity |
|--------------------------|----------------------|---------------------------|---------------|----------|
| {admin_func} | {precondition} | {user_action} | {window} | {assess} |

**Sui-specific patterns**:
- Admin migration requires exclusive access to shared object, but users continuously submit transactions against it (consensus ordering interleaves admin with user txs)
- Migration function requires all user positions withdrawn, but users can re-deposit between admin's check and migration
- Owned objects (user positions) cannot be accessed by admin functions -- migration requires user cooperation
- Dynamic fields on shared objects added by users must be cleaned up before object deletion

If blocking is possible AND permanent -> minimum MEDIUM severity
If blocking is temporary but repeatable -> assess with Rule 10 worst-state

---

## Step 5: External Package Verification

For each external package dependency:

| External Package | Published Version | Upgrade Policy | Our Package Pins To | Compatible With Our Usage? |
|-----------------|-------------------|---------------|---------------------|---------------------------|
| {package_id} | {version} | {compatible/additive/immutable} | {specific version or latest} | YES/NO |

**Check**:
- If external package upgrades, do our function calls still work?
- Are we using types from the external package that could change?
- Does the external package's `UpgradeCap` holder pose a risk?

---

## Step 6: Downstream Integration Compatibility

| Protocol Change | Downstream Consumer Type | Expected Interface/Type | Actual Post-Migration | Breaking Change? |
|----------------|------------------------|------------------------|----------------------|-----------------|
| {change} | Other Sui packages (composability) | {expected type} | {actual type} | YES/NO |
| {change} | Indexers/explorers | {expected event structure} | {actual} | YES/NO |
| {change} | Frontend/SDK | {expected PTB structure} | {actual} | YES/NO |
| {change} | PTB composers (DeFi aggregators) | {expected function signature} | {actual} | YES/NO |

**Sui-specific**: PTB composability means other protocols may compose our public functions into their PTBs. If function signatures or return types change, ALL downstream PTB composers break silently.

---

## Key Questions (Must Answer All)

1. **Type Identity**: For each shared object, is the type from the old package or new? Are they the same type (upgrade lineage) or different?
2. **Old Code Still Callable**: What old package functions remain callable post-upgrade? Can any be used maliciously against shared objects?
3. **Migration Completeness**: Can ALL V1-era objects and assets be migrated to or accessed by V2 paths?
4. **Dynamic Field Compatibility**: Are all dynamic field key types accessible from the new package version?
5. **Stranded Path**: Is there any combination of (old_object + new_logic) that traps funds?

## Common False Positives

1. **True upgrade with type preservation**: Package upgrade via UpgradeCap with `compatible` policy preserves existing types
2. **Intentional immutability**: Old package deliberately left callable as a legacy compatibility layer
3. **Version guard pattern**: Shared objects contain a version field, old functions abort on version mismatch
4. **Admin-controlled migration**: Stranded assets recoverable via AdminCap-gated functions

## Output Schema

```markdown
## Finding [MG-N]: Title

**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: checkmark1,2,3,4,5,6 | xN(reason) | ?N(uncertain)
**Rules Applied**: [R4:___, R9:___, R10:___, R13:___]
**Severity**: Critical/High/Medium/Low/Info
**Location**: sources/{module}.move:LineN

**Object Transition**:
- Old: {old_package::module}
- New: {new_package::module}
- Mismatch Point: {where types/versions diverge}

**Description**: What is wrong
**Impact**: What can happen (stranded funds, bypassed security, broken composability)
**Evidence**: Code showing mismatch

### Precondition Analysis (if PARTIAL/REFUTED)
**Missing Precondition**: [What blocks exploitation]
**Precondition Type**: STATE / ACCESS / TIMING / EXTERNAL / BALANCE

### Postcondition Analysis (if CONFIRMED/PARTIAL)
**Postconditions Created**: [What conditions this creates]
**Postcondition Types**: [List applicable types]
```

---

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Identify Token Transitions | YES | | Upgrade lineage vs separate deployment |
| 2. Check Interface Compatibility | YES | | Sui upgrade policy rules |
| 3. Trace Token Flow Paths | YES | | |
| 3b. External Side Effect Compatibility | YES | | |
| 3c. Pre-Upgrade Object Inventory | YES | | |
| 4. Stranded Asset Analysis (4a-4e) | YES | | All four scenarios modeled |
| 4f. User-Blocks-Admin Scenarios | YES | | |
| 5. External Package Verification | YES | | |
| 6. Downstream Integration Compatibility | YES | | |

If any step skipped, document valid reason (N/A, immutable package, single version, no downstream consumers).

## references/sui/object-ownership.md

---
name: "object-ownership"
description: "Trigger Pattern Always required for Sui Move audits -- object lifecycle and ownership model - Inject Into Breadth agents, depth-state-trace, depth-token-flow"
---

# OBJECT_OWNERSHIP Skill

> **Trigger Pattern**: Always required for Sui Move audits -- object lifecycle and ownership model
> **Inject Into**: Breadth agents, depth-state-trace, depth-token-flow
> **Finding prefix**: `[OO-N]`
> **Rules referenced**: R4, R5, R9, R10, R13

Sui's object-centric model is fundamentally different from account-based chains. Every struct with the `key` ability is an on-chain object with a globally unique ID, and its ownership model (owned/shared/frozen/wrapped) determines who can access and mutate it. Incorrect ownership choices, missing transfer restrictions, orphaned UIDs, and uncontrolled dynamic fields are the primary Sui-specific vulnerability classes.

---

## 1. Object Inventory

For EVERY struct with `key` ability in the codebase, build this table:

| # | Object Name (Module) | Abilities | Ownership Model | Created Where | Transferred Where | Destroyed Where | Has `store`? |
|---|---------------------|-----------|-----------------|---------------|-------------------|-----------------|-------------|
| 1 | {name} ({module}) | {key, store, ...} | OWNED / SHARED / FROZEN / WRAPPED / MIXED | {function:line} | {function:line or NEVER} | {function:line or NEVER} | YES/NO |

**Ability rules**:
- `key` alone: Object can exist on-chain but CANNOT be transferred by generic `transfer::public_transfer` (requires module-defined transfer logic).
- `key + store`: Object CAN be transferred by anyone via `transfer::public_transfer`. This is a **permissive** choice -- verify it is intentional.
- `key + store + copy`: Object can be duplicated -- extremely rare for value-bearing objects. FLAG if found on any object holding balances.
- `key + store + drop`: Object can be silently discarded without calling a destructor. FLAG if the object holds `Balance<T>` or other value -- tokens can be lost.

**Ownership model classification**:
- **OWNED**: Created and transferred to a specific address via `transfer::transfer` or `transfer::public_transfer`. Only the owner can pass it as a transaction argument.
- **SHARED**: Made accessible to all via `transfer::public_share_object`. Any transaction can read/write it. CRITICAL access control implications.
- **FROZEN**: Made immutable via `transfer::public_freeze_object`. Anyone can read, no one can mutate.
- **WRAPPED**: Stored as a field inside another object (not directly addressable on-chain). Accessible only through the parent.
- **MIXED**: Object starts as one type and transitions to another (e.g., created as owned, then shared). Document the transition path.

---

## 2. Ownership Model Analysis

### 2a. Owned Object Audit

For each OWNED object:

| Object | Should Be Shared Instead? | Ownership Transfer Possible? | Transfer Restriction Correct? | Assumption Risk |
|--------|--------------------------|-----------------------------|-----------------------------|----------------|
| {name} | YES/NO ({reason}) | YES (has `store`) / NO (no `store`) | {analysis} | {risk if ownership changes} |

**Check patterns**:
- **Should this be shared?** If multiple unrelated parties need to mutate the object in the same epoch, owned model creates bottlenecks or requires trust delegation. Common mistake: config objects that should be shared are kept owned, forcing single-admin bottleneck.
- **Ownership change undermines assumptions?** If code assumes "only admin holds AdminCap", but AdminCap has `store` ability, it can be transferred to anyone. Verify that transfer does not break invariants downstream.
- **Phantom ownership**: Object is "owned" but the owner address is a PDA-like derived address that nobody controls (e.g., `@0x0`). The object is effectively inaccessible -- equivalent to locked funds if it holds value.

### 2b. Shared Object Audit

For each SHARED object:

| Object | Mutation Functions | Access Guards | Concurrent Mutation Risk | Ordering Dependency |
|--------|-------------------|---------------|------------------------|-------------------|
| {name} | {list all functions that take `&mut` ref} | {what prevents unauthorized mutation} | YES/NO ({analysis}) | YES/NO ({analysis}) |

**CRITICAL checks**:
- **Access control on mutation**: Shared objects can be passed as arguments by ANY transaction. If a function takes `&mut SharedObj` without verifying the caller has authority (e.g., checking a capability object), anyone can mutate it. This is the #1 Sui vulnerability pattern.
- **Consensus ordering**: Transactions touching the same shared object are ordered by Sui's consensus. If the protocol relies on specific transaction ordering (e.g., "admin sets fee before user trades"), front-running is possible because consensus ordering is non-deterministic from the user's perspective.
- **Race conditions**: Two transactions that both mutate the same shared object field can produce different final states depending on execution order. If the protocol assumes sequential access, this is a bug.
- **Gas-based DoS**: An attacker can submit many transactions touching a shared object to increase contention and gas costs for legitimate users.

### 2c. Frozen Object Audit

For each FROZEN object:

| Object | Should Updates Be Possible? | Freezing Reversible? | Data Staleness Risk |
|--------|-----------------------------|---------------------|-------------------|
| {name} | YES/NO ({reason}) | NO (by design) | {risk if frozen data becomes stale} |

**Check**: If frozen object holds configuration that may need updating (fee rates, oracle addresses, admin keys), freezing is likely wrong -- should be shared with access control instead.

### 2d. Wrapped Object Audit

For each WRAPPED object:

| Parent Object | Wrapped Object | Unwrap Path Exists? | Dynamic Fields on Wrapped? | Destruction Safety |
|--------------|---------------|--------------------|--------------------------|--------------------|
| {parent} | {wrapped} | YES ({function}) / NO | YES/NO | {what happens to wrapped when parent destroyed} |

**Check patterns**:
- **No unwrap path**: If a wrapped object holds value (Balance, Coin) but there is no function to extract it, funds are permanently locked inside the parent. Apply Rule 9: stranded asset = minimum MEDIUM.
- **Parent destruction without unwrap**: If the parent object can be destroyed (has `drop` ability or explicit destructor) without first unwrapping/extracting the inner object, the inner object's value is lost.
- **Dynamic fields on wrapped objects**: Dynamic fields added to a wrapped object's UID are NOT accessible when the object is wrapped. They become orphaned until unwrap. If the protocol adds dynamic fields and then wraps, those fields are inaccessible.

---

## 3. Object Transfer Analysis

For each `transfer::transfer`, `transfer::public_transfer`, `transfer::share_object`, `transfer::public_share_object`, `transfer::freeze_object`, `transfer::public_freeze_object` call:

| # | Transfer Call | Object Type | Initiator | `store` Required? | `store` Present? | Recipient Validation | Stranded Risk |
|---|-------------|------------|-----------|-------------------|-----------------|---------------------|---------------|
| 1 | {function:line} | {type} | {who calls} | YES (public_*) / NO (module-only) | YES/NO | {is recipient validated?} | {can object be sent to address that cannot use it?} |

**Check patterns**:
- **`store` ability gate**: `transfer::public_transfer` requires `store`. `transfer::transfer` does not -- it is module-restricted. If an object should NOT be freely transferable by holders, it should NOT have `store`.
- **Recipient validation**: If an object is transferred to an arbitrary address and that address does not have the matching module to use it, the object is stranded. This is especially dangerous for capability objects (AdminCap sent to a contract that cannot invoke admin functions).
- **Transfer to self**: Transferring an object to the transaction sender is sometimes used as a "commit" pattern. Verify this does not bypass any state transitions.
- **Conditional transfer**: If transfer happens inside a conditional branch, check the else branch -- does the object leak (neither transferred, shared, frozen, wrapped, nor destroyed)?

---

## 4. Shared Object Mutation Safety

For each shared object, build the mutation map:

| Shared Object | Function | Mutation Type | Guard | Re-entrancy Risk | Ordering Sensitivity |
|--------------|----------|--------------|-------|-----------------|---------------------|
| {obj} | {func} | FIELD_UPDATE / BALANCE_CHANGE / CHILD_ADD / CHILD_REMOVE | {capability check, address check, or NONE} | {can another function on same object be called mid-execution?} | {does outcome depend on call order?} |

**Sui-specific re-entrancy note**: Move's borrow checker prevents re-entrancy within a single module (you cannot pass `&mut Obj` to a function that also borrows `&mut Obj`). However, cross-module re-entrancy is possible if Object A's mutation calls a function in Module B that calls back to Module A with a different entry point that accesses a DIFFERENT shared object whose state is coupled with Object A.

**Concurrent mutation checklist**:
- [ ] Can two independent transactions mutate the same field to conflicting values?
- [ ] Does the protocol rely on reading a value from the shared object and then writing back a derived value? (TOCTOU with consensus ordering)
- [ ] Can an attacker observe a pending transaction on a shared object and submit a competing transaction that front-runs it?
- [ ] Are balance operations on shared objects atomic? (Balance::split + Balance::join should not be interruptible across objects)

---

## 5. Object Wrapping/Unwrapping

For each wrapping relationship (object stored as field in another object):

| Wrapper | Wrapped | Wrap Point | Unwrap Point | Dynamic Fields Before Wrap | UID Preserved on Unwrap? |
|---------|---------|-----------|-------------|--------------------------|-------------------------|
| {parent} | {child} | {function:line} | {function:line or NONE} | YES/NO | YES/NO/N/A |

**Check patterns**:
- **Dynamic field orphaning**: If `dynamic_field::add(child_uid, ...)` is called before `child` is wrapped into `parent`, those dynamic fields become inaccessible. They still exist on-chain (consuming storage) but cannot be read or removed until the child is unwrapped.
- **UID preservation**: When an object is unwrapped and re-created, does it get the same UID or a new one? If new UID, all dynamic fields on the old UID are orphaned permanently.
- **Nested wrapping depth**: Objects wrapped inside objects wrapped inside objects create deep access chains. Each level adds complexity and potential for state inconsistency.
- **Balance preservation invariant**: If the wrapped object holds `Balance<T>`, verify that total balance is preserved across wrap/unwrap cycles. No balance should be created or destroyed during wrapping.

---

## 6. UID Lifecycle Audit

Every call to `object::new(ctx)` creates a UID. Every UID must be either:
1. Stored in an object with `key` ability (the object's `id` field), OR
2. Explicitly destroyed via `object::delete(id)`

For each `object::new(ctx)` call:

| # | Creation Location | UID Stored In | Destruction Location | Lifecycle Complete? | Orphan Risk |
|---|------------------|--------------|---------------------|--------------------|-----------|
| 1 | {function:line} | {object field or VARIABLE} | {function:line or NONE} | YES/NO | {if NO: resource leak} |

**Check patterns**:
- **Orphaned UID**: If `object::new(ctx)` is called but the resulting UID is not stored in an object that gets transferred/shared/frozen, and not deleted, it is a resource leak. The UID exists on-chain consuming storage but is unreachable.
- **UID in error paths**: If a function creates a UID, then hits an abort/assert before storing it -- the transaction reverts, so no leak. BUT if the function returns early (non-abort) with the UID in a local variable -- this is a compiler error in Move (linear type), so it should not be possible. Verify the compiler catches this.
- **UID reuse**: A UID should never be reused after `object::delete`. Move's type system should prevent this, but verify in any `unsafe` or `native` code paths.
- **Dynamic field cleanup before delete**: Before calling `object::delete(id)`, ALL dynamic fields on that UID should be removed. Otherwise, the dynamic fields become permanently orphaned (the UID no longer exists to access them through). Apply Rule 9: orphaned dynamic fields holding value = stranded assets = minimum MEDIUM.

---

## 7. Dynamic Field Audit

For each `dynamic_field::add`, `dynamic_field::remove`, `dynamic_object_field::add`, `dynamic_object_field::remove`:

| # | Operation | Parent UID | Field Name/Type | Value Type | Access Control | Unbounded Growth? | Cleanup on Delete? |
|---|-----------|-----------|----------------|-----------|---------------|-------------------|-------------------|
| 1 | ADD | {parent:line} | {name type + value} | {type} | {who can add} | YES/NO | {is field removed before parent UID deleted?} |

**Check patterns**:
- **Unbounded growth**: If `dynamic_field::add` is called in a loop or user-facing function without a cap, the parent object's dynamic field set grows without limit. This increases gas costs for operations that iterate related state and can be used as a DoS vector.
- **Unauthorized field addition**: If ANY caller can add dynamic fields to a shared object's UID, an attacker can pollute the object's field namespace. This may cause `dynamic_field::borrow` to return unexpected data if field names collide.
- **Name collision**: Dynamic fields are keyed by `(TypeTag, name_value)`. If two different code paths add fields with the same key type and value, they overwrite each other. Verify field name uniqueness across all add operations on the same UID.
- **Type safety**: `dynamic_field::borrow<Name, Value>` will abort if the stored value type does not match `Value`. Verify all borrow calls use consistent type parameters with the corresponding add calls.
- **Object fields vs value fields**: `dynamic_object_field::add` stores objects (with `key` ability) that retain their own UID and are independently addressable. `dynamic_field::add` wraps values. Using the wrong variant can make objects inaccessible or create unexpected behavior.
- **Removal completeness**: Before an object is destroyed (`object::delete`), ALL dynamic fields must be removed. Build a removal completeness table:

| Parent Object | Destruction Function | Dynamic Fields Added | Dynamic Fields Removed Before Delete | Complete? |
|--------------|---------------------|---------------------|--------------------------------------|----------|
| {obj} | {func:line} | {list all add operations} | {list all remove operations in destructor} | YES/NO |

---

## Finding Template

```markdown
## Finding [OO-N]: Title

**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: checkmark1,2,3,4,5,6,7 | x(reasons) | ?(uncertain)
**Rules Applied**: [R4:Y/N, R5:Y/N, R9:Y/N, R10:Y/N, R13:Y/N]
**Severity**: Critical/High/Medium/Low/Info
**Location**: sources/{module}.move:LineN
**Description**: [Specific ownership/lifecycle issue with code reference]
**Impact**: [What can happen -- fund loss, state corruption, DoS, stranded assets]

### Precondition Analysis (if PARTIAL or REFUTED)
**Missing Precondition**: [What blocks this attack]
**Precondition Type**: STATE / ACCESS / TIMING / EXTERNAL / BALANCE
**Why This Blocks**: [Specific reason]

### Postcondition Analysis (if CONFIRMED or PARTIAL)
**Postconditions Created**: [What conditions this creates]
**Postcondition Types**: [STATE, ACCESS, TIMING, EXTERNAL, BALANCE]
**Who Benefits**: [Who can use these]
```

---

## Step Execution Checklist (MANDATORY)

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Object Inventory | YES | Y/N/? | Every struct with `key` ability |
| 2a. Owned Object Audit | IF owned objects exist | Y/N(none)/? | Transfer restriction + assumption risk |
| 2b. Shared Object Audit | IF shared objects exist | Y/N(none)/? | Access control on mutation |
| 2c. Frozen Object Audit | IF frozen objects exist | Y/N(none)/? | Staleness risk |
| 2d. Wrapped Object Audit | IF wrapped objects exist | Y/N(none)/? | Unwrap path + value preservation |
| 3. Object Transfer Analysis | YES | Y/N/? | Every transfer/share/freeze call |
| 4. Shared Object Mutation Safety | IF shared objects mutated | Y/N(none)/? | Concurrent mutation + ordering |
| 5. Object Wrapping/Unwrapping | IF wrapping relationships exist | Y/N(none)/? | Dynamic field orphaning + UID preservation |
| 6. UID Lifecycle Audit | YES | Y/N/? | Every `object::new` matched to storage or delete |
| 7. Dynamic Field Audit | IF dynamic fields used | Y/N(none)/? | Growth bounds + cleanup completeness |

### Cross-Reference Markers

**After Section 2b (Shared Object Audit)**: Feed unguarded mutation functions to SEMI_TRUSTED_ROLES skill if roles are involved in access control.

**After Section 3 (Transfer Analysis)**: Feed objects with `store` ability to TOKEN_FLOW_TRACING skill for balance flow analysis.

**After Section 6 (UID Lifecycle)**: Feed orphaned UIDs and incomplete dynamic field cleanup to depth-edge-case for stranded asset analysis (Rule 9).

**After Section 7 (Dynamic Field Audit)**: Feed unbounded growth patterns to ECONOMIC_DESIGN_AUDIT for DoS cost analysis.

## references/sui/oracle-analysis.md

---
name: "oracle-analysis"
description: "Trigger Pattern ORACLE flag (required) - Inject Into Breadth agents, depth-external, depth-edge-case"
---

# ORACLE_ANALYSIS Skill (Sui)

> **Trigger Pattern**: ORACLE flag (required)
> **Inject Into**: Breadth agents, depth-external, depth-edge-case
> **Purpose**: Analyze oracle integrations in Sui Move protocols for staleness, decimal handling, failure modes, and manipulation vectors

For every oracle the protocol consumes:

**STEP PRIORITY**: Steps 6 (Failure Modes) and 5c (Deviation Reference) are where HIGH/CRITICAL severity findings most commonly hide. Do NOT rush these steps. If constrained, skip conditional sections (4a-4d, 5a) before skipping 5c or 6.

## 1. Oracle Inventory

Enumerate ALL oracle data sources the protocol reads:

| Oracle | Type | Module / Object | Functions Called | Consumers (protocol functions) | Update Frequency | Heartbeat |
|--------|------|-----------------|-----------------|-------------------------------|-----------------|-----------|
| {name} | Pyth / Switchboard V2 / Supra / Custom / On-chain TWAP | {module::function or shared object ID} | {get_price / get_price_no_older_than / etc.} | {list all} | {expected} | {documented or UNKNOWN} |

**For each oracle**: What decision does the protocol make based on this data? (pricing, liquidation threshold, reward rate, share price, swap amount, etc.)

**Sui-specific inventory checks**:
- Is the oracle data passed as a shared object parameter (`&PriceInfoObject`) or read from on-chain state?
- Does the protocol use `pyth::price_info::get_price_info_from_price_info_object()` or a wrapper?
- Are oracle objects passed by reference (`&`) or by mutable reference (`&mut`)?
- **Shared object contention**: Oracle objects like Pyth's `PriceInfoObject` are shared objects. During high-volatility periods, multiple transactions compete to read/update the same oracle object, causing transaction ordering dependencies and potential stale reads due to sequencing delays.

## 2. Staleness Analysis

For each oracle identified in Step 1:

### 2a. Staleness Checks Present?

| Oracle | Timestamp Checked? | Max Staleness Enforced? | Staleness Threshold | Clock Source | Appropriate? |
|--------|-------------------|------------------------|--------------------:|-------------|-------------|
| {name} | YES/NO | YES/NO | {seconds or NONE} | {clock::timestamp_ms / custom} | {analysis} |

**CRITICAL -- Sui uses MILLISECONDS**: `clock::timestamp_ms(clock)` returns milliseconds, not seconds. Pyth's `price.timestamp` returns seconds. If the protocol compares these without unit conversion, the staleness check is 1000x too lenient or too strict. Check for:
- `publish_time` (seconds from Pyth) vs `clock::timestamp_ms()` (milliseconds) -- MUST convert one to match the other
- Direct subtraction without unit normalization
- Constants like `MAX_STALENESS` -- is the value in seconds or milliseconds?

**Correct Sui staleness pattern**:
```move
// Pyth on Sui -- check price freshness
let price = pyth::price_info::get_price_info_from_price_info_object(price_info_object);
let price_data = price_info::get_price_feed(&price);
let current_price = price_feed::get_price(price_data);
let timestamp = price::get_timestamp(&current_price); // SECONDS
let now_ms = clock::timestamp_ms(clock); // MILLISECONDS -- Clock is shared object at 0x6
let now_s = now_ms / 1000; // Convert to seconds to match Pyth
assert!(now_s - timestamp <= MAX_STALENESS_SECONDS, E_STALE_PRICE);
```

**If NO staleness check**: What happens when the oracle returns stale data?
- [ ] Protocol uses stale price for liquidations -- unfair liquidations
- [ ] Protocol uses stale price for minting/deposits -- mispriced assets
- [ ] Protocol uses stale price for swaps -- arbitrage opportunity
- [ ] Protocol uses stale price for rewards -- incorrect distribution

### 2b. Stale Data Impact Trace

For each consumer function, trace the impact of receiving data that is {heartbeat x 2} old:

| Consumer Function | Data Used | If Stale By {X}: Impact | Severity |
|-------------------|-----------|------------------------|----------|
| {function} | {price/rate} | {specific impact} | {H/M/L} |

### 2c. Pyth-Specific Checks

| Check | Code Reference | Status |
|-------|---------------|--------|
| `get_price()` return values checked? | {location} | YES/NO |
| `price.price` validated > 0? | {location} | YES/NO |
| `price.conf` (confidence interval) checked? | {location} | YES/NO |
| `price.expo` (exponent) handled correctly? | {location} | YES/NO |
| `price.timestamp` staleness validated? | {location} | YES/NO |
| `get_price_no_older_than()` used vs manual check? | {location} | YES/NO |
| Pyth price update fee paid via `Coin<SUI>`? | {location} | YES/NO |

### 2d. Switchboard V2-Specific Checks

| Check | Code Reference | Status |
|-------|---------------|--------|
| `aggregator::latest_value()` return validated? | {location} | YES/NO |
| Result timestamp checked for staleness? | {location} | YES/NO |
| Decimal scaling applied correctly? (Switchboard custom decimals per feed) | {location} | YES/NO |
| Min response count checked? | {location} | YES/NO |
| Aggregator authority validated? | {location} | YES/NO |

### 2e. Supra-Specific Checks

| Check | Code Reference | Status |
|-------|---------------|--------|
| Price feed ID validated? | {location} | YES/NO |
| Decimal precision from feed metadata used? | {location} | YES/NO |
| Timestamp freshness checked? | {location} | YES/NO |
| Round completeness verified? | {location} | YES/NO |

## 3. Decimal Normalization Audit

For each oracle data flow:

| Oracle | Oracle Decimals / Exponent | Consumer Expects | Normalization Applied? | Correct? |
|--------|---------------------------|-----------------|----------------------|----------|
| {name} | {e.g., expo = -8 for Pyth} | {expected by math} | YES/NO | {analysis} |

**Pyth exponent handling**: Pyth returns `Price { price: i64, conf: u64, expo: i32, timestamp: u64 }`. The `expo` field is typically negative (e.g., -8 means price has 8 decimal places). Verify:
- Is `expo` read dynamically or assumed to be a fixed value?
- Is the sign of `expo` handled correctly (negative exponent = division, positive = multiplication)?
- Does `10^|expo|` computation overflow for large exponents?

**MANDATORY GREP**: Search all oracle consumer modules for hardcoded decimal constants: `1_000_000_000`, `100_000_000`, `1_000_000`, `10_000`, `1e8`, `1e6`, `1e9`, `DECIMAL`, `PRECISION`. For each hit: (1) Is this a decimal normalization constant? (2) Does it match the ACTUAL oracle's exponent? (3) If the oracle feed changes exponent, does this constant break?

**Decimal chain trace**: For each arithmetic operation using oracle data, trace the full decimal chain: `oracle_output_decimals` -> `normalization_step` -> `consumer_expected_decimals`. If any step uses a hardcoded constant rather than reading the exponent dynamically -> FINDING.

**Common Sui decimal mismatches**:
- Pyth price expo = -8, but protocol assumes -18 (or vice versa)
- SUI has 9 decimals (`MIST_PER_SUI = 1_000_000_000`)
- USDC on Sui has 6 decimals
- Cross-multiplication without normalization: `price * amount` where price and amount have different decimal bases

### 3d. Decimal Grep Sweep (MECHANICAL -- MANDATORY)
Grep ALL oracle consumer modules for `10_|decimals|PRECISION|SCALE|expo|pow`. For each match, fill:

| File:Line | Pattern | Hardcoded Value | Oracle's Actual Decimals/Expo | Match? |
|-----------|---------|-----------------|------------------------------|--------|

If ANY row shows Match=NO or oracle decimals UNKNOWN with hardcoded constant -> FINDING (R16).
Skipping this step is a Step Execution violation (x3d).

<!-- LOAD_IF: TWAP -->
## 4. TWAP-Specific Analysis

If protocol uses any TWAP oracle (on-chain pool observation, custom TWAP accumulator, etc.):

### 4a. TWAP Window Analysis

| TWAP Oracle | Window Length | Pool Liquidity | Manipulation Cost (est.) | Sufficient? |
|-------------|-------------|----------------|-------------------------|-------------|
| {oracle} | {seconds} | {USD value} | {estimated} | YES/NO |

**Rule of thumb**: TWAP window < 30 min AND pool TVL < $10M -> potentially manipulable.
**Sui-specific**: On Sui, TWAP typically relies on CLOB or AMM observation points. Check if the TWAP source is a CLOB (orderbook DEX) or AMM. CLOB TWAPs can be manipulated with limit orders that are never filled.

### 4b. TWAP Arithmetic

| Check | Status | Impact if Wrong |
|-------|--------|-----------------|
| Overflow protection on cumulative price difference? | YES/NO | {impact} |
| Geometric vs arithmetic mean -- correct for use case? | {which used} | {impact if wrong} |
| Time-weighted vs observation-count-weighted? | {which} | {manipulation vector} |
| Empty observation slots handled? | YES/NO | {impact} |

### 4c. TWAP Lagging Behavior

During rapid price movements, TWAP lags spot price. Trace:
- What happens when TWAP price is significantly lower than spot? (discounted minting/borrowing)
- What happens when TWAP price is significantly higher than spot? (premium liquidations)
- Is this lag exploitable by attackers who can predict the direction?

### 4d. TWAP Cold-Start Analysis

Check oracle behavior when history is insufficient: (1) zero snapshots, (2) single snapshot, (3) window period not yet elapsed.

| Cold-Start State | Oracle Return Value | Protocol Behavior | Exploitable? |
|------------------|--------------------:|-------------------|-------------|

For each exploitable state: can attacker act during cold-start window at manipulated price? Tag: [BOUNDARY:snapshots=0], [BOUNDARY:snapshots=1].
If TWAP returns 0 or aborts during cold-start with no fallback -> FINDING (R16, minimum Medium).
<!-- END_LOAD_IF: TWAP -->

## 5. Oracle Weight / Threshold Boundaries

For multi-oracle systems or oracle-based thresholds:

<!-- LOAD_IF: MULTI_ORACLE -->
### 5a. Multi-Oracle Systems

| Oracle System | Aggregation Method | Oracle Count | Agreement Required | What if Disagreement? |
|---------------|-------------------|-------------|-------------------|----------------------|
| {system} | Median / Mean / Weighted / First-valid | {N} | {M of N} | {fallback behavior} |

**Check**: What happens at exact threshold boundaries?
- If median of [100, 100, 101]: result = 100. Is that correct?
- If weighted average with equal weights rounds down: impact?
- If one oracle aborts: does fallback handle it gracefully?
<!-- END_LOAD_IF: MULTI_ORACLE -->

### 5b. Oracle-Based Thresholds

| Threshold | Oracle Data Used | Threshold Value | At Exact Boundary | Off-by-One? |
|-----------|-----------------|----------------|-------------------|-------------|
| {name} | {oracle field} | {value} | {behavior at exact value} | YES/NO |

**Check `>` vs `>=`**: At the exact threshold value, does the protocol behave as intended?

### 5c. Deviation Reference Point Audit

For each deviation check in the protocol (max_deviation, price_deviation, deviation_threshold, etc.):

| Parameter | Measured Against | Reference Source | Reference Manipulable? | Reference Staleable? |
|-----------|-----------------|-----------------|----------------------|---------------------|

Checks:
1. What is the deviation MEASURED AGAINST? (previous on-chain price, TWAP, external oracle, hardcoded value)
2. Is the reference point itself manipulable? (e.g., if deviation checks current vs last-recorded, and last-recorded is admin-settable -> admin can set a stale reference that makes all future prices "within deviation")
3. Can the reference become stale? (e.g., if reference is updated only on specific actions, and those actions stop occurring)
4. Is the first recorded price special? (no prior reference -> deviation check may be bypassed on first update)
Tag: `[TRACE:deviation check: current vs {reference} -> reference source: {X} -> manipulable: {Y/N}]`

## 6. Oracle Failure Modes

For each oracle, model failure scenarios:

| Failure Mode | Oracle Behavior | Protocol Response | Impact | Mitigation Present? |
|-------------|-----------------|-------------------|--------|-------------------|
| Zero return | Returns price = 0 | {what happens} | {impact} | YES/NO |
| Abort | Function call aborts | {what happens} | {impact} | YES/NO -- caught? |
| Stale (heartbeat exceeded) | Returns old data | {what happens} | {impact} | YES/NO -- staleness check? |
| Extreme value | Returns outlier | {what happens} | {impact} | YES/NO -- bounds check? |
| Negative price (Pyth i64) | Returns < 0 | {what happens} | {impact} | YES/NO -- sign check? |
| High confidence interval | conf > threshold | {what happens} | {impact} | YES/NO -- conf check? |
| Price update not called | PriceInfoObject never refreshed | {what happens} | {impact} | YES/NO -- update enforced? |

**Sui-specific failure**: Pyth on Sui requires explicit price update transactions (`pyth::update_price_feeds`). If the protocol does not enforce fresh updates before reading, prices can be arbitrarily stale. Check:
- Does the protocol call `update_price_feeds` in the same PTB before reading?
- Or does it rely on external keepers to update? If so, what if keepers stop?
- Does the protocol use `get_price_no_older_than()` which enforces freshness?

**For each unmitigated failure mode**: What is the worst-case impact? Can it lead to fund loss?

**Circuit breaker check**: Does the protocol have a mechanism to pause oracle-dependent operations if the oracle enters a failure state?

## Finding Template

```markdown
**ID**: [OR-N]
**Severity**: [based on fund impact and likelihood of oracle failure/manipulation]
**Step Execution**: check1,2,3,4,5,6 | x(reasons) | ?(uncertain)
**Rules Applied**: [R1:check, R4:check, R10:check, R16:check]
**Location**: module::function:LineN
**Title**: Oracle [issue type] in [function] enables [attack/failure]
**Description**: [Specific oracle issue with data flow trace]
**Impact**: [Quantified impact under worst-case oracle scenario]
```

## Instantiation Parameters
```
{CONTRACTS}           -- Move modules to analyze
{ORACLE_MODULES}      -- Oracle integration modules (pyth, supra, custom)
{PRICE_OBJECTS}       -- Shared oracle objects (PriceInfoObject, etc.)
{CONSUMER_FUNCTIONS}  -- Functions that read oracle data
{ORACLE_TYPE}         -- Pyth / Supra / Switchboard / Custom
{HEARTBEAT}           -- Expected update frequency
```

## Output Schema
| Field | Required | Description |
|-------|----------|-------------|
| oracle_inventory | yes | All oracle sources and consumers |
| staleness_analysis | yes | Staleness checks and impact |
| decimal_audit | yes | Decimal normalization correctness |
| failure_modes | yes | Each oracle's failure scenarios |
| finding | yes | CONFIRMED / REFUTED / CONTESTED |
| evidence | yes | Code locations with line numbers |
| step_execution | yes | Status for each step |

---

## Step Execution Checklist (MANDATORY)

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Oracle Inventory | YES | check/x/? | |
| 2. Staleness Analysis | YES | check/x/? | For each oracle |
| 3. Decimal Normalization Audit | YES | check/x/? | |
| 3d. Decimal Grep Sweep | YES | check/x/? | MANDATORY mechanical step |
| 4. TWAP-Specific Analysis | IF TWAP used | check/x(N/A)/? | |
| 4d. TWAP Cold-Start Analysis | IF TWAP used | check/x(N/A)/? | Zero/single snapshot states |
| 5. Oracle Weight / Threshold Boundaries | IF multi-oracle or thresholds | check/x(N/A)/? | |
| 5c. Deviation Reference Point Audit | IF deviation checks exist | check/x(N/A)/? | Reference manipulability |
| 6. Oracle Failure Modes | YES | check/x/? | For each oracle |

## references/sui/package-version-safety.md

---
name: "package-version-safety"
description: "Trigger Pattern PACKAGE_UPGRADE flag (UpgradeCap detected, multiple package versions, upgrade policy references) - Inject Into Breadth agents, depth-external"
---

# Skill: PACKAGE_VERSION_SAFETY (Sui)

> **Trigger Pattern**: PACKAGE_UPGRADE flag (UpgradeCap detected, multiple package versions, upgrade policy references)
> **Inject Into**: Breadth agents, depth-external
> **Finding prefix**: `[PV-N]`
> **Rules referenced**: R4, R8, R9, R10

Sui packages are immutable once published. "Upgrading" a package means publishing a NEW version at a NEW on-chain address, linked to the original via the UpgradeCap lineage. The old version's code remains callable forever. This creates a fundamentally different upgrade risk model compared to EVM proxies: instead of replacing logic in-place, Sui packages accumulate versions -- and shared objects may be accessible by ALL versions simultaneously.

---

## Trigger Patterns

```
UpgradeCap|upgrade_policy|package::make_immutable|compatible|additive|dep_only|version|
migrate|old_version|new_version
```

---

## Step 1: Upgrade Policy Inventory

For each package in scope:

| # | Package | UpgradeCap Location | UpgradeCap Holder | Has `store`? | Upgrade Policy | Destroyed? |
|---|---------|--------------------|--------------------|-------------|---------------|-----------|
| 1 | {pkg_name} | {init function:line} | {address / shared / wrapped in governance} | YES/NO | {compatible/additive/dep_only} | YES (immutable) / NO |

**Checks**:
- **Where is UpgradeCap stored?**
  - Owned by deployer address: Single point of failure. Key loss -> permanent immutability. Key theft -> attacker can upgrade.
  - Shared object: DANGEROUS -- anyone can pass it to upgrade functions.
  - Wrapped in governance object: Good pattern -- upgrade requires governance approval.
  - Destroyed via `make_immutable()`: Package is permanently immutable. No upgrade risk.
  - Transferred to `@0x0` or burn address: Effectively immutable.

- **Can UpgradeCap be transferred?**
  - UpgradeCap has `key + store` by default -> freely transferable via `public_transfer`.
  - Is there a custom wrapper restricting transfer? (e.g., `GovernanceCap` wrapping `UpgradeCap`)
  - If transferable and held by EOA -> attacker stealing key can transfer UpgradeCap.

- **Can UpgradeCap be destroyed?**
  - `sui::package::make_immutable(cap)` consumes UpgradeCap -> permanent immutability.
  - If UpgradeCap has `drop` via wrapper -> accidental destruction possible.

### 1b. UpgradeCap Governance Assessment

| Governance Model | Risk Level | Assessment |
|-----------------|------------|------------|
| Single EOA | CRITICAL | One key compromise replaces all package logic |
| Multisig (2/3 or lower) | HIGH | Low collusion threshold |
| Multisig (3/5+) | MEDIUM | Requires majority collusion |
| Multisig + timelock | LOW | Users can exit before malicious upgrade takes effect |
| DAO/governance contract | LOW | Distributed control, but check voter distribution |
| Destroyed (immutable) | NONE | Cannot upgrade, but also cannot patch bugs |

---

## Step 2: Version Consistency Check

For shared objects created by this package:

| Shared Object | Created By (Version) | Current Version Field? | V1 Functions Access? | V2 Functions Access? | Consistency Risk |
|--------------|---------------------|----------------------|---------------------|---------------------|-----------------|
| {obj_type} | V1 `init()` | YES: `version: u64` / NO | {list funcs} | {list funcs} | {describe} |

**What happens when package is upgraded?**
- Existing shared objects created by V1 remain at their original address
- V2 functions CAN access V1-created shared objects (types are preserved in compatible upgrades)
- V1 functions are STILL callable and CAN access the same shared objects
- This dual-access is the primary version safety concern

**Can old-version and new-version calls on same shared object create inconsistency?**
- V1 function writes field A based on formula F1
- V2 function writes field A based on formula F2
- User calls V1 then V2 in separate transactions -> field A has inconsistent state
- **Especially dangerous**: V2 adds a new check that V1 lacks. Attacker calls V1 to bypass V2's check.

---

## Step 3: Dependency Version Pinning

For each dependency in `Move.toml`:

| Dependency | Source | Pinned To | Immutable? | Upgrade Risk |
|-----------|--------|-----------|-----------|-------------|
| Sui Framework | `sui = "..."` | {git rev or latest} | Upgraded by validators | Framework upgrade could change behavior |
| MoveStdlib | `MoveStdlib = "..."` | {git rev} | Upgraded with framework | Same as above |
| {third_party} | {git url or on-chain} | {specific rev / branch / on-chain version} | YES/NO | {describe} |

**Checks**:
- Are third-party dependencies pinned to specific git revisions? If pinned to `main` -> upstream changes included on recompile.
- For on-chain published dependencies: is the dependency package immutable? If it has active UpgradeCap -> behavior can change.
- Can a dependency upgrade break our package's invariants?
- Are there transitive dependencies with their own upgrade risks?

**Can dependency upgrade break our package?**
- Compatible dependency upgrade: function implementations can change but signatures preserved. Our calls still compile but behavior may differ.
- Additive dependency upgrade: only new functions/types added. Existing behavior frozen.
- Framework upgrades: `sui::*` packages upgraded by validators. Can change Move VM behavior, gas costs, object model rules.

---

## Step 4: Type Compatibility Across Versions

When package V2 adds new types or fields:

| Type | V1 Definition | V2 Changes | Compatible Upgrade Rule | Migration Needed? |
|------|-------------|-----------|------------------------|------------------|
| {struct_name} | {fields} | {cannot change for compatible} | Struct layouts FROZEN | NO -- same layout |
| {new_struct} | N/A | {new in V2} | New types allowed | N/A |

**Sui type rules for compatible upgrades**:
- Existing struct field layouts CANNOT change (enforced by validator during upgrade)
- New structs CAN be added
- Existing function signatures CANNOT change
- Function bodies CAN change (this is where logic vulnerabilities occur)
- Generic type parameters must remain the same

**Can V1 objects be used with V2 functions?**
- YES for compatible upgrades: types are identical, V2 functions accept V1 objects.
- NO for separate package deployment: different package address = different types.

**Can V2 objects be used with V1 functions?**
- V2 does not create new object types that V1 knows about (V1 code is frozen).
- But V2 functions can modify shared objects that V1 functions then read -- state corruption possible.

**Dynamic field implications**:
- Dynamic fields keyed by type. If V2 changes key/value types for dynamic fields -> V1-era entries orphaned.
- Check: does V2 change any dynamic field key types?

---

## Step 5: Upgrade Migration Safety

Does the package have migration functions to update shared objects from V1->V2 state?

| Migration Function | Trigger | What It Updates | Reversible? | Access Control |
|-------------------|---------|----------------|-----------|---------------|
| {migrate_func} | {admin call / automatic} | {version field, new state} | NO | {AdminCap / anyone} |

**Version guard pattern**: Shared objects contain `version: u64`. V1 functions check `assert!(version == 1)`. V2 migration function sets `version = 2`. After migration, V1 functions abort because version != 1.

**Check**:
- Is version guard implemented? If NOT -> old functions remain callable indefinitely -> FINDING.
- Can migration be triggered by unauthorized parties?
- Is migration atomic? Can it be partially completed?
- What happens to user-owned objects during migration? (Owned objects cannot be modified by admin migration)

---

## Step 6: UpgradeCap Governance

If UpgradeCap is owned by a single address:

| Risk | Description | Severity |
|------|-------------|----------|
| **Full logic replacement** | Attacker upgrades package with malicious code. All shared objects now interact with attacker's logic. | CRITICAL |
| **Subtle parameter change** | Attacker upgrades to change a fee calculation or threshold in function body. Hard to detect. | HIGH |
| **Dependency manipulation** | Attacker upgrades to change dependency versions, pulling in vulnerable code. | HIGH |
| **Policy escalation blocked** | Upgrade policies can only be tightened (compatible -> additive -> dep_only -> immutable). Attacker cannot escalate from additive to compatible. | Mitigation |

**Mitigations to check**:
- [ ] Is UpgradeCap behind multisig?
- [ ] Is there an upgrade timelock (users can exit before upgrade takes effect)?
- [ ] Is there a multi-party approval mechanism for upgrades?
- [ ] Has the upgrade policy been tightened from the default `compatible`?
- [ ] Is `make_immutable()` called in `init()` for packages that should never upgrade?

---

## Key Questions (Must Answer All)

1. **UpgradeCap security**: Who holds it? What is the attack surface if compromised?
2. **Upgrade policy**: Is the policy appropriate? Could it be tightened without losing needed functionality?
3. **Cross-version bypass**: Can old functions bypass security checks added in new versions?
4. **Version guard**: Is there a mechanism to disable old functions after upgrade?
5. **Type compatibility**: Are all types compatible? Are dynamic fields accessible across versions?

---

## Common False Positives

1. **Immutable package**: UpgradeCap destroyed or `make_immutable` called -> no upgrade risk
2. **No shared objects**: If old and new packages share no objects, cross-version interaction impossible
3. **Version guard implemented**: Shared objects check version field, old functions abort after migration
4. **Capability migrated**: Old package's capabilities consumed by new package, old functions uncallable
5. **`additive` or `dep_only` policy**: Existing logic frozen (but new functions can still access shared objects -- check Step 3c)

---

## Output Schema

```markdown
## Finding [PV-N]: Title

**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: check1,2,3,4,5,6 | skip(reason) | uncertain
**Rules Applied**: [R4:___, R8:___, R9:___, R10:___]
**Severity**: Critical/High/Medium/Low/Info
**Location**: sources/{module}.move:LineN

**Upgrade Risk Type**: UPGRADECAP_MANAGEMENT / POLICY_INAPPROPRIATE / CROSS_VERSION_BYPASS / TYPE_INCOMPATIBILITY / DEPENDENCY_RISK / MISSING_VERSION_GUARD
**Package Version**: V{N} -> V{N+1}
**Shared Objects Affected**: {list}

**Description**: What is wrong
**Impact**: What can happen (logic replacement, security bypass, stranded assets, type mismatch)
**Evidence**: Code showing vulnerability
**Recommendation**: How to fix (tighten policy, add version guard, migrate capabilities, destroy UpgradeCap)
```

---

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Upgrade Policy Inventory | YES | | UpgradeCap location, holder, policy |
| 1b. UpgradeCap Governance Assessment | YES | | Risk level for each package |
| 2. Version Consistency Check | YES | | All shared objects checked for dual-version access |
| 3. Dependency Version Pinning | YES | | Move.toml analyzed |
| 4. Type Compatibility Across Versions | YES | | Dynamic fields included |
| 5. Upgrade Migration Safety | YES | | Version guard pattern checked |
| 6. UpgradeCap Governance | IF single-address holder | | Multisig/timelock/approval checks |

### Cross-Reference Markers

**After Step 1**: If UpgradeCap held by single address -> immediate finding (minimum HIGH).

**After Step 2**: If cross-version bypass possible -> cross-reference with MIGRATION_ANALYSIS Step 5 for shared object function enumeration.

**After Step 3**: Feed dependency risks to DEPENDENCY_AUDIT for transitive dependency analysis.

**After Step 5**: If no version guard AND shared objects hold user funds -> minimum HIGH finding.

If any step skipped, document valid reason (N/A, package is immutable, no shared objects, no third-party dependencies).

## references/sui/ptb-composability.md

---
name: "ptb-composability"
description: "Trigger Pattern PTB flag (always for Sui -- Programmable Transaction Blocks are the Sui transaction model) - Inject Into Breadth agents, depth-external, depth-state-trace"
---

# Skill: PTB_COMPOSABILITY (Sui)

> **Trigger Pattern**: PTB flag (always for Sui -- Programmable Transaction Blocks are the Sui transaction model)
> **Inject Into**: Breadth agents, depth-external, depth-state-trace
> **Finding prefix**: `[PTB-N]`
> **Rules referenced**: R4, R5, R8, R10, R15

Programmable Transaction Blocks (PTBs) are Sui's transaction composition primitive. A single PTB can contain up to 1024 commands, each calling a different function, with return values routed between commands. This enables atomic multi-step sequences that are fundamentally different from EVM's single-entry-point model. Every `public` function is a potential PTB command -- there is no "internal only" visibility equivalent to Solidity's `internal`.

**STEP PRIORITY**: Steps 1 (Single-Call Assumption Audit) and 4 (Atomic Read-Modify-Write) are where HIGH/CRITICAL severity findings most commonly hide. Do NOT rush these steps. If constrained, skip conditional sections (6, 7) before skipping 1, 3, or 4.

---

## 1. Entry Point Inventory

For EVERY `public` and `entry` function in the protocol, classify composability:

| # | Function | Module | Visibility | Returns Value? | Takes Shared Obj? | Composable in PTB? | Notes |
|---|----------|--------|-----------|---------------|-------------------|-------------------|-------|
| 1 | {func} | {mod} | public / entry / public(package) | YES / NO | YES / NO | YES / NO | {context} |

**Sui visibility semantics**:
- `entry` functions: callable from PTB but return values CANNOT be used by subsequent commands (consumed or discarded within the command). Objects can only be transferred, not returned.
- `public` functions: fully composable -- return values pass between commands. This is the primary composability surface.
- `public(package)`: callable only by other modules in the same package. NOT callable from PTB. Safe from external composition.
- `fun` (private): Not callable from outside the module. Safe.

**Key observation**: Any function that is `public` (not `entry`, not `public(package)`) is fully composable. Its return values can be routed to ANY other function in the same PTB.

### 1b. Single-Call Assumption Audit

For EVERY `public` and `entry` function, check whether its security model implicitly assumes it is the ONLY function called in the transaction:

| # | Function | Assumes Single-Call? | What Assumption? | Breakable via PTB? | Impact |
|---|----------|---------------------|-----------------|-------------------|--------|
| 1 | {func} | YES/NO | {describe assumption} | YES/NO | {impact} |

**Common single-call assumptions that PTBs break**:
- **Post-call state check**: Function reads state, performs action, then a SEPARATE function checks the result. Attacker inserts commands between action and check.
- **Balance snapshot**: Function reads a balance at start, assumes balance changed only due to its work. Another PTB command could have changed the balance.
- **One-operation-per-transaction**: Protocol assumes a user can only deposit OR withdraw in a single transaction. PTB allows deposit + borrow + withdraw atomically.
- **Temporal separation**: Protocol assumes time must pass between action A and action B (cooldown). If both functions are callable, PTB executes them in the same transaction with zero time delta.
- **Reentrancy-like patterns**: Function A updates state partially, function B reads partial state. PTBs naturally enable "call A then call B" -- state IS updated between commands.

**Key question for each function**: "If an attacker calls this function as command N in a PTB, and can execute arbitrary commands 1..N-1 before it and N+1..1024 after it, what can go wrong?"

---

## 2. Multi-Step Composition Analysis

For each `public` function that returns objects:

| # | Function | Input Objects | Output Objects | Can Output Be Routed To? | Can Be Called Multiple Times? |
|---|----------|-------------|---------------|-------------------------|----------------------------|
| 1 | {func} | {owned/shared/immutable} | {Coin<T> / Object / HotPotato} | {any function accepting this type} | YES/NO |

**Return value routing checks**:
- **Coin routing**: If function A returns `Coin<T>`, can it be routed to function C (external) instead of intended function B?
- **Coin splitting**: PTB has native `SplitCoins` command. Returned `Coin<T>` can be split. Does protocol assume full amount passed?
- **Coin merging**: PTB has native `MergeCoins` command. Can attacker merge extra coins to inflate amounts?
- **Mutable reference routing**: `&mut Object` references are borrowed for a command and released after. Same object can be passed to multiple commands sequentially. Does command N assume the object wasn't modified by command N-1?
- **Recipient mismatch**: If function returns a value-bearing object, the final `TransferObjects` command determines the recipient.

### 2b. Value Interception Pattern

Model this specific attack for each function returning value:

```
1. Attacker calls protocol_function_A() -> returns Coin<USDC> (intended for pool)
2. Attacker routes Coin<USDC> to their own address via TransferObjects
3. Protocol expects the Coin was consumed by the next step but it was intercepted
```

**Check**: Does the protocol rely on PTB command ordering to ensure returned values reach the right destination? If YES -> the user controls the ordering, not the protocol.

---

## 3. Flash Loan via PTB

Without explicit flash loan protocols, PTBs enable flash-loan-like patterns:

```
1. [Command 1] Split large Coin<SUI> from attacker's balance
2. [Command 2] Deposit into protocol (inflates TVL/balance)
3. [Command 3] Trigger reward distribution (calculated on inflated balance)
4. [Command 4] Withdraw from protocol
5. [Command 5] Join coins back (net zero capital, captured rewards)
```

**Can protocol state be manipulated between PTB commands?**

| Function Pair | Shared State | Deposit-Action-Withdraw Possible? | Defense? |
|--------------|-------------|----------------------------------|---------|
| {deposit, claim} | {pool balance} | YES/NO | {cooldown? same-epoch check? minimum lock?} |

**Hot potato flash loan pattern**:
```
1. borrow(pool, amount) -> (Coin<T>, FlashReceipt)     // Creates hot potato
2. [attacker does arbitrary operations with Coin<T>]
3. repay(pool, coin, receipt)                            // Consumes hot potato
```

**Checks for hot potato flash loans**:
- Does `repay()` verify returned amount >= borrowed amount + fee?
- Can attacker call OTHER protocol functions between borrow and repay that benefit from temporarily inflated balance?
- Is `FlashReceipt` type-parameterized to prevent cross-pool receipt reuse?
- Verify hot potato struct has NO abilities (no `key`, `store`, `drop`, `copy`)

---

## 4. Shared Object Mutation Ordering

Within a PTB touching shared objects:

### 4a. Oracle Manipulation Within PTB

```
1. [Command 1] Call DEX swap to move on-chain price
2. [Command 2] Call protocol function that reads manipulated price
3. [Command 3] Reverse DEX swap to restore price
4. Net effect: Protocol acted on manipulated price, attacker profited
```

**Check**: Does protocol read spot prices (manipulable) or TWAP/oracle prices (resistant)?

### 4b. Balance Manipulation Within PTB

```
1. [Command 1] Deposit large amount (inflate balance/shares)
2. [Command 2] Trigger reward distribution (on inflated balance)
3. [Command 3] Withdraw deposited amount
4. Net effect: Captured rewards with zero time commitment
```

### 4c. State Toggling Within PTB

```
1. [Command 1] Set state to value A (e.g., via AdminCap function)
2. [Command 2] Perform action requiring state A
3. [Command 3] Reset state back to original value B
4. Net effect: Privileged action performed, state appears unchanged
```

**Check**: Possible if attacker controls an AdminCap? (Rule 6: semi-trusted role analysis)

### 4d. Shared Object Reorder Sensitivity

| Shared Object | Invariant | Commands That Mutate It | Sequence-Dependent? | Exploitable? |
|--------------|-----------|------------------------|-------------------|-------------|
| {obj} | {invariant description} | {cmd1, cmd2, cmd3} | YES/NO | {if YES, describe exploit} |

**Check for each shared object**: Write down the invariant. Can a sequence of 2-3 valid individual operations (each maintaining the invariant) produce a combined state that violates the invariant?

---

## 5. Hot Potato Enforcement

For each zero-ability struct (hot potato):

| # | Hot Potato Type | Created By | Consumed By | Abilities | Bypass Possible? |
|---|----------------|-----------|-------------|-----------|-----------------|
| 1 | {Receipt} | {borrow()} | {repay()} | NONE (confirmed) | {analysis} |

**Checks**:
- [ ] Verify struct has NO abilities. If it has `drop` -> NOT a hot potato.
- [ ] Is the consuming function the ONLY function accepting this type?
- [ ] Can multiple hot potatoes be created in a single PTB? Does consumption order matter?
- [ ] Does consuming function validate the hot potato matches the creating context?
- [ ] Can attacker cause consumption precondition to fail AFTER hot potato created? (entire PTB aborts -- griefing vector)
- [ ] Can a wrapper with `store` ability store the hot potato? (bypass via external module -- check for public generic wrappers)

---

## 6. Object Wrapping/Unwrapping in PTB

Can objects be wrapped, manipulated, and unwrapped within a single PTB to bypass checks?

| Wrap Operation | Unwrap Operation | What's Inside | Check Bypassed? |
|---------------|-----------------|-------------|----------------|
| {wrap_func} | {unwrap_func} | {object with restrictions} | {describe bypass or NONE} |

**Pattern**: Object has transfer restrictions (`key` only, no `store`). Attacker wraps it inside a `store`-capable struct, transfers the wrapper, then unwraps on the other side. If wrap and unwrap are both `public` functions -> transfer restriction bypassed within a single PTB.

---

## 7. Gas Budget Manipulation

PTB gas budget is shared across all commands.

| Attack Vector | Description | Check |
|--------------|-------------|-------|
| **Many-small-operations** | 1000+ tiny operations to circumvent aggregate limits | Do aggregate limits track across PTB commands? |
| **Object creation spam** | PTB creates many objects (up to 1024 per PTB) | Unbounded object creation paths? |
| **Gas exhaustion griefing** | Craft PTB that consumes max gas on revert | Gas charged on abort -- attacker pays |

**Typically lower severity**: Sui's gas model charges the sender, so resource attacks are self-penalizing. Focus on aggregate limit bypass.

---

## Output Schema

```markdown
## Finding [PTB-N]: Title

**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: check1,2,3,4,5,6,7 | skip(reason) | uncertain
**Rules Applied**: [R4:___, R5:___, R8:___, R10:___, R15:___]
**Severity**: Critical/High/Medium/Low/Info
**Location**: sources/{module}.move:LineN

**PTB Attack Type**: SINGLE_CALL_ASSUMPTION / RETURN_VALUE_ROUTING / FLASH_LOAN_VIA_PTB / ATOMIC_MANIPULATION / HOT_POTATO_BYPASS / WRAP_UNWRAP_BYPASS / AGGREGATE_LIMIT
**Attack Sequence**:
1. [Command 1]: {what attacker does}
2. [Command 2]: {what attacker does}
3. [Command N]: {what attacker does}
**Net Effect**: {what the attacker gained}

**Description**: What is wrong
**Impact**: What can happen (fund loss, unfair value capture, invariant violation)
**Evidence**: Code showing vulnerability + PTB command sequence
```

---

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Entry Point Inventory | YES | | All public/entry functions classified |
| 1b. Single-Call Assumption Audit | YES | | **HIGH PRIORITY** -- every public function checked |
| 2. Multi-Step Composition Analysis | YES | | All return values checked |
| 2b. Value Interception Pattern | YES | | Value-returning functions modeled |
| 3. Flash Loan via PTB | YES | | Deposit-action-withdraw patterns |
| 4. Shared Object Mutation Ordering | YES | | **HIGH PRIORITY** -- oracle, balance, state toggle |
| 4d. Shared Object Reorder Sensitivity | YES | | Invariant + multi-command sequences |
| 5. Hot Potato Enforcement | IF hot potatoes exist | | Zero-ability verified |
| 6. Object Wrapping/Unwrapping | IF wrap/unwrap functions exist | | Transfer restriction bypass |
| 7. Gas Budget Manipulation | IF aggregate limits exist | | |

### Cross-Reference Markers

**After Step 1b**: If single-call assumption found on fund-critical function -> immediate HIGH finding.

**After Step 3**: Cross-reference with SHARE_ALLOCATION_FAIRNESS for deposit-action-withdraw reward capture.

**After Step 4a**: Cross-reference with ORACLE_ANALYSIS if on-chain DEX prices are used.

**After Step 5**: Cross-reference with ABILITY_ANALYSIS Section 5 (Hot Potato Enforcement).

If any step skipped, document valid reason (N/A, no hot potatoes, no external calls, no aggregate limits).

## references/sui/semi-trusted-roles.md

---
name: "semi-trusted-roles"
description: "Trigger Pattern SEMI_TRUSTED_ROLE flag (required) - Inject Into Breadth agents, depth-state-trace"
---

# Skill: Semi-Trusted Role Analysis (Sui)

> **Trigger Pattern**: SEMI_TRUSTED_ROLE flag (required)
> **Inject Into**: Breadth agents, depth-state-trace
> **Purpose**: Analyze capability-based privilege model in Sui Move protocols for both role-to-user and user-to-role attack vectors

## Trigger Patterns
```
AdminCap|OwnerCap|TreasuryCap|OperatorCap|KeeperCap|GovernanceCap|
MinterCap|ManagerCap|UpgradeCap|PublisherCap|has_cap|assert_cap|
capability|cap_check|admin_only
```

## Reasoning Template

### Step 1: Inventory Role Permissions

Enumerate ALL capability objects in the protocol:

| Capability Type | Abilities | Holder | Functions Callable | State Modifiable | Transferable? |
|----------------|-----------|--------|-------------------|-----------------|---------------|
| {AdminCap} | {key, store?} | {deployer/multisig} | {list all functions requiring &AdminCap} | {list state} | {YES if has store / NO if only key} |

**Sui capability model**:
- Capabilities are owned objects. Holding the object = having the role.
- `key` ability: object can exist on-chain. Transferred with `transfer::transfer` (module-only) or `transfer::public_transfer` (if also has `store`).
- `key + store`: freely transferable by anyone. **High risk** -- capability can be sent to arbitrary addresses.
- `key` only: transferable only by the defining module's functions. **Lower risk** -- controlled transfer.
- Capabilities are checked by reference: `fun admin_action(cap: &AdminCap, ...)` -- presence of reference proves ownership.

For each capability at {CAPABILITY_OBJECTS}:
- What state does it grant access to modify?
- What external calls does it authorize?
- What parameters does it allow setting?
- Is the capability shared (`share_object`) or owned? Shared caps are NOT single-holder.

### Step 2: Analyze Within-Scope Abuse

For each permitted action, ask:

**Timing Abuse**:
- Can {ROLE_NAME} execute at harmful times? (front-run users via shared object contention, during rebalance)
- Can {ROLE_NAME} delay execution to harm users? (withhold keeper actions)

**Parameter Abuse**:
- Can {ROLE_NAME} pass harmful parameters? (max slippage, wrong recipient, extreme fee)
- Are parameters validated against bounds, or trusted implicitly?

**Sequence Abuse**:
- Can {ROLE_NAME} execute operations out of order?
- Can {ROLE_NAME} skip required operations in a multi-step flow?

**Omission Abuse**:
- Can {ROLE_NAME} harm users by NOT acting? (skip price update, delay distribution, not calling keeper function)

### Step 3: Model Attack Scenarios
```
Scenario A: Timing Attack
1. {ROLE_NAME} monitors pending transactions on shared objects
2. {ROLE_NAME} submits transaction to modify shared object state
3. Due to Sui's object-based execution, contention determines ordering
4. User's transaction executes with worse conditions
5. Impact: {TIMING_IMPACT}

Scenario B: Parameter Attack
1. {ROLE_NAME} calls {ROLE_FUNCTION} with {MALICIOUS_PARAMS}
2. Parameters are not validated against {EXPECTED_CONSTRAINTS}
3. Impact: {PARAM_IMPACT}

Scenario C: Key Compromise / Capability Theft
1. {ROLE_NAME} capability object is transferred to attacker
2. If cap has `store` ability: attacker can receive it via `transfer::public_transfer`
3. Attacker can call: {ROLE_FUNCTIONS}
4. Maximum extractable value: {MAX_DAMAGE}
5. Recovery options: {RECOVERY_PATH}
   - Can a higher-level cap revoke or re-create the compromised cap?
   - Is there an UpgradeCap that can patch the module?

Scenario C2: Shared Capability Abuse
1. {ROLE_NAME} capability is a shared object (created via `transfer::share_object`)
2. ANY user can include the shared cap in their PTB as `&SharedCap` reference
3. Attacker calls admin functions by passing the shared cap reference
4. Attacker can atomically compose admin operations with exploitation in a single PTB
5. Maximum extractable value: {MAX_DAMAGE}
6. NOTE: Shared caps effectively give EVERYONE the admin role -- this is almost always a critical finding
```

### Step 4: Assess Mitigations

| Mitigation | Present? | Effective? |
|------------|----------|------------|
| Timelock on {ROLE_NAME} actions | YES/NO | {clock-based delay?} |
| Multisig ownership (Sui multisig or custom) | YES/NO | {threshold?} |
| Removal/revocation function for {ROLE_NAME} | YES/NO | {who can revoke?} |
| Rate limits or cooldowns (clock-based) | YES/NO | {duration?} |
| Parameter bounds enforcement | YES/NO | {min/max checked?} |
| UpgradeCap held separately | YES/NO | {who holds it?} |

**Does a removal/revocation function for {ROLE_NAME} EXIST?** If NO -> FINDING: capability is irrevocable without module upgrade. Severity: minimum Medium if cap can modify user-facing state.

**Capability transfer control**:
- If cap has `store`: anyone holding it can transfer freely. Is this intended?
- If cap has only `key`: only module functions can transfer. Are those functions properly access-controlled?
- Is there a `destroy` function for the capability? If NO and cap has `store`: it can never be burned.
- Is the capability frozen (immutable via `transfer::freeze_object`)? Frozen caps can be read (`&Cap`) but not consumed or mutated -- limits authorized actions to read-only gating.

**PTB composition risk**: Can the capability holder compose a PTB that atomically: (1) changes parameters via admin function, (2) exploits the changed parameters via user function? If YES and cap is owned by a semi-trusted role -> the role can atomically manipulate + exploit without time for users to react.

### Step 5: Model User-Side Exploitation (Reverse Direction)

**Predictability Analysis**:
- Is the role's behavior predictable? (scheduled tasks, triggered by events, epoch-based)
- Can users observe when the role will act via on-chain state?
- Can users front-run or back-run the role's actions via shared object contention?

**Scenario D: User Exploits Keeper Timing**
```
1. User observes that {ROLE_NAME} executes {ROLE_ACTION} at predictable times (e.g., epoch boundaries)
2. User positions themselves before {ROLE_ACTION} (deposit/stake before reward distribution)
3. {ROLE_ACTION} executes, changing state
4. User benefits from known state change
5. Impact: {USER_EXPLOIT_IMPACT}
```

**Scenario E: User Griefs Role Preconditions**
```
1. {ROLE_FUNCTION} has precondition: {PRECONDITION} (stored in shared object)
2. User calls a permissionless function that modifies the shared object to violate {PRECONDITION}
3. {ROLE_NAME} calls {ROLE_FUNCTION}, which aborts
4. System enters degraded state (no keeper actions possible)
5. Impact: {GRIEF_IMPACT}
```

**Scenario F: User Forces Suboptimal Role Action**
```
1. {ROLE_NAME} must choose between options based on shared object state
2. User manipulates shared object state to make worst option appear best
3. {ROLE_NAME} (following honest behavior) chooses suboptimal path
4. User profits from forced suboptimal execution
5. Impact: {SUBOPTIMAL_IMPACT}
```

**Scenario G: Same-Chain Rate Staleness via Discrete Updates**
```
1. Protocol's exchange rate only updates when {ROLE_NAME} acts (discrete updates)
2. Between role actions, rate is stale -- does not reflect accumulated value
3. User monitors for {ROLE_NAME} pending transaction on shared object
4. User enters at stale rate (favorable), {ROLE_NAME} executes, rate updates
5. User exits at updated rate (or holds appreciating position)
6. Impact: {RATE_ARBIT_IMPACT}
```

### Step 6: Precondition Griefability Check

For each function callable by {ROLE_NAME}:

| Function | Preconditions | Stored In | User Can Manipulate? | Grief Impact |
|----------|--------------|-----------|---------------------|--------------|
| {func} | balance > 0 | Shared pool object | YES - withdraw all | Keeper stuck |
| {func} | epoch elapsed | Clock (0x6) | NO - time-based | N/A |
| {func} | threshold met | Shared config object | YES - partial withdraw | Delayed execution |

**Generic Rule**: Any admin/keeper function precondition that depends on user-modifiable shared object state is potentially griefable.

**Sui-specific griefability**: Shared object contention can cause transaction ordering issues. If a keeper transaction and a user transaction both touch the same shared object, Sui's consensus determines ordering -- neither party can guarantee priority.

### Step 6b: Admin/Privileged Function Griefability (EXHAUSTIVE)

**MANDATORY**: Enumerate ALL functions that require a capability parameter. Do NOT rely on manual scanning -- grep for all capability types found in Step 1.

For each function requiring ANY capability:

| Function | Required Cap | Preconditions | Shared Object Dependency? | User Can Manipulate? | Grief Impact |
|----------|-------------|--------------|---------------------------|---------------------|--------------|
| {admin_fn} | {AdminCap} | {preconditions} | YES/NO | YES/NO | {impact if griefed} |

**Enumeration completeness check**:
- [ ] Grep count for functions accepting capability references: {N}
- [ ] Functions analyzed in this table: {M}
- [ ] If M < N -> INCOMPLETE -- analyze missing functions before proceeding

**Specific checks**:
- Can users create shared object state that blocks admin operations? (pending withdrawals blocking migration, non-zero balances blocking cleanup)
- Can users create dynamic field entries that block operations? (table entries preventing deletion)
- Can users initiate multi-step operations whose in-flight state blocks admin actions?

**RULE**: If ANY admin function has a user-griefable precondition -> severity >= MEDIUM if it blocks critical protocol operations.

### Key Questions (must answer all)
1. What is the maximum damage if {ROLE_NAME} acts maliciously?
2. What is the maximum damage if {ROLE_NAME} capability is stolen?
3. Are there time-sensitive operations where {ROLE_NAME} timing matters?
4. What user funds or protocol state can {ROLE_NAME} affect?
5. Can users predict when {ROLE_NAME} will act?
6. Can users manipulate preconditions to block {ROLE_NAME}?
7. Can users profit by positioning around {ROLE_NAME}'s scheduled actions?
8. What happens if {ROLE_NAME} cannot execute? (system degradation)
9. Can users block admin operations via shared object state manipulation?

## Common False Positives

- **View-only operations**: If role can only read state, no abuse vector
- **Idempotent operations**: If calling twice has same effect as once, timing abuse is limited
- **User-initiated dependency**: If role action requires user to initiate first, front-running may not apply
- **Economic alignment**: If role is economically aligned (staked collateral), malicious action has cost
- **Module-locked capability**: If cap has only `key` and no transfer function exists, theft requires module compromise

## Instantiation Parameters
```
{CONTRACTS}           -- Move modules to analyze
{ROLE_NAME}           -- Specific capability type (AdminCap, OperatorCap, etc.)
{CAPABILITY_OBJECTS}  -- Capability object types and their abilities
{ROLE_FUNCTIONS}      -- Functions this capability grants access to
{USER_ACTION}         -- User action that could be front-run
{ROLE_ACTION}         -- Role action used in attack
{TIMING_IMPACT}       -- Impact of timing attack
{MALICIOUS_PARAMS}    -- Harmful parameter values
{EXPECTED_CONSTRAINTS}-- What params should be validated against
{PARAM_IMPACT}        -- Impact of parameter attack
{MAX_DAMAGE}          -- Maximum extractable value
{RECOVERY_PATH}       -- How to recover from compromise
```

## Output Schema
| Field | Required | Description |
|-------|----------|-------------|
| capability_inventory | yes | All capability objects and their permissions |
| timing_vectors | yes | Timing-based abuse opportunities |
| parameter_vectors | yes | Parameter-based abuse opportunities |
| omission_vectors | yes | Harm from inaction |
| user_exploit_vectors | yes | How users can exploit the role (reverse direction) |
| transfer_risk | yes | Capability transferability analysis |
| max_damage | yes | Worst-case damage assessment |
| mitigations | yes | Existing protections |
| finding | yes | CONFIRMED / REFUTED / CONTESTED / NEEDS_DEPTH |
| evidence | yes | Code locations with line numbers |
| step_execution | yes | Status for each step |

---

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Inventory Role Permissions | YES | | |
| 2. Analyze Within-Scope Abuse | YES | | |
| 3. Model Attack Scenarios (A,B,C,C2) | YES | | Including shared cap scenario |
| 4. Assess Mitigations | YES | | |
| 5. Model User-Side Exploitation (D,E,F,G) | **YES** | | **MANDATORY** -- never skip |
| 6. Precondition Griefability Check | **YES** | | **MANDATORY** -- never skip |
| 6b. Admin Function Griefability | **YES** | | **MANDATORY** -- never skip |

### Cross-Reference Markers

**After Step 4** (Assess Mitigations):
- **DO NOT STOP HERE** -- Steps 5-6 analyze the reverse direction
- IF role has any preconditions depending on shared object state -> **MUST complete Step 6**

**After Step 5** (User-Side Exploitation):
- Cross-reference with `TOKEN_FLOW_TRACING.md` for token-related griefing vectors
- IF keeper actions are predictable -> document MEV/front-running vectors

**After Step 6** (Precondition Griefability):
- IF any precondition is user-griefable -> severity >= MEDIUM
- Document system degradation if keeper is blocked

## references/sui/share-allocation-fairness.md

---
name: "share-allocation-fairness"
description: "Trigger Pattern SHARE_ALLOCATION flag detected in pattern scan - Inject Into Breadth agents, depth-edge-case"
---

# Skill: Share Allocation Fairness (Sui)

> **Trigger Pattern**: SHARE_ALLOCATION flag detected in pattern scan
> **Inject Into**: Breadth agents, depth-edge-case
> **Finding prefix**: `[SAF-N]`
> **Rules referenced**: R5, R10, R13, R14

```
shares|allocation|distribute|pro.rata|proportional|vest|reward.*per.*share|
balance::join|balance::split|coin::mint|reward_index|cumulative|epoch.*reward
```

## Purpose
Analyze fairness of share/token allocation mechanisms on Sui where users receive Coin<T> shares or Balance<T> proportional to deposits, contributions, or participation -- checking for late-entry advantages, PTB-based timing exploitation, queue-position gaming, and time-weighting omissions.

**Sui-specific share representations**:
- `Coin<ShareToken>`: Fungible, freely transferable (has `key + store`). User holds as owned object.
- `Balance<ShareToken>` inside a position object: Non-transferable accounting. Locked within the protocol's object model.
- `u64` field in a shared/owned struct: Simple numerical tracking, no token representation.
- Check which representation is used -- it affects transferability, composability, and gaming vectors.

---

## Methodology

### STEP 1: Classify Allocation Mechanism

Identify which pattern the protocol uses:

| Type | Sui Pattern | Key Risk |
|------|------------|----------|
| Pro-rata snapshot | Shares minted at fixed ratio via `balance::split`/`coin::mint_balance` at deposit time | Late depositors dilute early depositors' accrued value |
| Time-weighted | Per-user owned object tracks `reward_per_share_paid` and `accrued_rewards` with `clock::timestamp_ms()` | Checkpoint manipulation, discrete vs continuous accrual |
| Queue-based | Table/VecMap in shared object stores pending deposits | Queue position gaming, PTB-based front-running |
| Epoch-based | Shares valued per Sui epoch boundary via `tx_context::epoch()` | Cross-epoch timing arbitrage at epoch transition |

### STEP 2: Late Entry Attack Model

For each allocation entry function:

1. **Identify accrual source**: What generates value for existing share holders? (yield from external DeFi, fees collected in shared pool, token emissions via TreasuryCap)
2. **Trace timing**: When does accrued value become claimable vs when can new shares enter? Is there a separate crank/update function?
3. **Check for time-weighting**: Does allocation account for HOW LONG shares were held, or only THAT shares are held at checkpoint time?
4. **Model attack**: Can a depositor enter AFTER value accrues but BEFORE distribution, capturing value they did not earn?

| Entry Function | Accrual Source | Time-Weighted? | Late Entry Possible? | Impact |
|---------------|----------------|----------------|---------------------|--------|

**Sui timing model**:
- `clock::timestamp_ms()` provides millisecond-precision timestamps (read from the `Clock` shared object)
- Timestamps advance per checkpoint (~0.5-2s), NOT per transaction
- Multiple transactions within the same checkpoint see the SAME timestamp
- Implication: time-weighted calculations based on `clock::timestamp_ms()` have ~0.5-2s granularity. An attacker can deposit and withdraw within the same checkpoint and see zero time elapsed, potentially capturing rewards with zero time commitment.

**PTB-specific timing**: With PTB composability, an attacker can compose multiple function calls in a single atomic transaction: [deposit] -> [trigger_distribution] -> [withdraw] within one PTB. This is more powerful than EVM flash loans for timing attacks because PTBs execute atomically with no inter-step cost.

#### STEP 2c: Cross-Address Deposit Model

For each entry function accepting a recipient address parameter:

| Entry Function | Accepts Recipient? | Default State for New Recipient | Exploitable? | Impact |
|---------------|-------------------|-------------------------------|-------------|--------|

**Check**: When a new position object or dynamic field is created for a recipient:
- What is the DEFAULT state? (`reward_per_share_paid = 0`? `last_deposit_epoch = 0`?)
- If `reward_per_share_paid` starts at 0 while the global index is at N, the new position holder captures ALL historical rewards on their deposit -- FINDING
- Can `deposit(recipient, coin)` where `recipient != sender` create a position that captures historical rewards the recipient did not earn?
- On Sui, this may manifest as a new owned object created for the recipient (clean state -- typically safe) or a new dynamic field added to a shared object keyed by address (check default values).

#### STEP 2d: Pre-Setter Timing Model

For each admin-settable reward/rate parameter:

| Parameter Setter | Cap Required | Staked-Before-Set? | Retroactive Rewards? | Fair? |
|-----------------|-------------|-------------------|---------------------|-------|

Model: user deposits (position created with current index) -> admin sets reward rate -> rewards accrue.
- Does the user receive retroactive rewards for the period BEFORE the rate was set?
- Is the global reward index updated atomically with the rate change in the same function call?

### 2e. Pre-Configuration State Analysis

For the allocation mechanism identified in Step 1:

| Configuration Step | Parameter Set | Functions Available Before Set | Exploitable Default? |
|--------------------|-------------|-------------------------------|---------------------|

1. What is the deployment/initialization sequence? In Sui, `init` runs once at package publish. What configuration happens in `init` vs subsequent admin transactions?
2. For each step: what functions are callable BEFORE this configuration completes?
3. Are there reward/share calculations that use unconfigured (zero/default) values in shared objects?
4. Can a user deposit/stake before full configuration and receive outsized rewards/shares?
5. Is there a version flag or `is_initialized` check that gates user interactions?

**Sui-specific**: `init()` runs atomically at publish. If configuration requires MULTIPLE transactions (init -> configure_pool -> set_rates), there are windows between these transactions where the protocol is partially configured.

If users can interact during partial configuration AND default values create unfair advantage -> FINDING (minimum Medium, Rule 13: design gap).

### STEP 3: Queue Position and Batch Processing

For protocols with batch/queue processing:

1. **Ordering fairness**: Is queue order FIFO (Table insertion order), arbitrary (admin-chosen), or manipulable (PTB composition order)?
2. **Partial processing**: Can admin process some deposits but not others within a batch? Does the batch function iterate with a limit?
3. **Cross-batch state**: Does processing order within a batch affect allocation ratios?
4. **Deposit splitting**: Can a user split one large `Coin<T>` into many small deposits (via `coin::split` in a PTB) for queue advantage or per-deposit limit bypass?

**Sui-specific ordering**:
- Transactions touching only owned objects are processed without consensus (fast path) -- no ordering manipulation
- Transactions touching shared objects go through consensus -- validator-influenced ordering within checkpoint
- PTB atomicity: all commands execute atomically, batch processing within a single PTB is all-or-nothing
- An attacker can use PTB to atomically: read queue state -> deposit at favorable position -> trigger processing -> claim

### STEP 4: Share Redemption Symmetry

Check that entry and exit use consistent valuation:

1. **Mint vs burn ratio**: Are shares minted at the same exchange rate they can be burned? (check share price calculation in both deposit and withdraw)
2. **Pending claims**: Can unclaimed reward Balance<T> dilute active shares' value? (rewards already owed but counted in TVL)
3. **Withdrawal queue**: Does withdrawal ordering create unfair priority?

**Sui-specific redemption**:
- If shares are `Coin<ShareToken>`, user burns them via protocol function. Check: can user transfer shares to another address and redeem there to bypass cooldowns?
- If shares are `Balance<ShareToken>` inside position object, redemption requires the position object. Check: can position object be transferred (has `store`?) to bypass restrictions?
- First-depositor / last-withdrawer edge cases: what happens when `total_supply == 0` and someone deposits? (division by zero in share calculation?)

**TreasuryCap authority risks**:
- Can TreasuryCap be used outside of deposit logic to inflate share supply?
- Is TreasuryCap stored in a shared object with access control? If `store` allows extraction from the wrapper -> unauthorized minting.
- If freeze authority pattern exists (rare on Sui): who controls it?

#### STEP 4b: Aggregate Constraint Coherence (Rule 14)

For independently-settable allocation rates/shares (e.g., per-pool weights, fee splits, distribution percentages):

| Rate/Weight Setter | Aggregate Constraint | Enforced On-Chain? | What if Sum Exceeds/Falls Short? |
|-------------------|---------------------|-------------------|--------------------------------|

**Sui-specific**: If weights are stored as dynamic fields on a shared object (one field per pool), the setter function may not iterate all fields to validate the sum. Check: does the setter read all weight dynamic fields and validate the total?

If aggregate constraint NOT enforced and rates independently settable -> FINDING (Rule 14).

---

## Output

For each finding, specify:
- Allocation mechanism type (pro-rata, time-weighted, queue, epoch)
- Whether time-weighting is present or missing
- Concrete attack sequence with numerical example (SUI/token amounts)
- Who benefits and who is harmed
- Whether the attack requires PTB composition or is achievable with single function calls
- Sui-specific timing factors (`clock::timestamp_ms()` granularity, checkpoint ordering)

## Finding Template

```markdown
**ID**: [SAF-N]
**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: (see checklist below)
**Rules Applied**: [R5:___, R10:___, R13:___, R14:___]
**Severity**: Critical/High/Medium/Low/Info
**Location**: sources/{module}.move:LineN
**Title**: {fairness violation type}
**Description**: {specific issue with numerical example}
**Impact**: {quantified at worst-state parameters -- who loses how much}
```

---

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Classify Allocation Mechanism | YES | | |
| 2. Late Entry Attack Model | YES | | PTB composition timing check |
| 2c. Cross-Address Deposit Model | YES | | Check recipient != sender patterns |
| 2d. Pre-Setter Timing Model | YES | | Model deposit-before-rate-set sequence |
| 2e. Pre-Configuration State Analysis | YES | | Post-init() window + unconfigured defaults |
| 3. Queue Position and Batch Processing | IF queue/batch detected | | Include PTB deposit splitting |
| 4. Share Redemption Symmetry | YES | | Include TreasuryCap access check |
| 4b. Aggregate Constraint Coherence | IF multiple settable weights | | Rule 14 enforcement check |

If any step skipped, document valid reason (N/A, no queue, single pool, no settable weights).

## references/sui/temporal-parameter-staleness.md

---
name: "temporal-parameter-staleness"
description: "Trigger Pattern TEMPORAL flag (required) - Inject Into Breadth agents, depth-state-trace"
---

# Skill: Temporal Parameter Staleness Analysis (Sui)

> **Trigger Pattern**: TEMPORAL flag (required)
> **Inject Into**: Breadth agents, depth-state-trace
> **Purpose**: Analyze cached parameters in multi-step operations on Sui for staleness when capability holders change them mid-operation. Time source on Sui is the shared Clock object at address 0x6.

## Trigger Patterns
```
epoch|period|duration|delay|cooldown|lock_period|timelock|
unbonding_period|claim_delay|withdraw_delay|maturity_time|
clock::timestamp_ms|tx_context::epoch
```

## Reasoning Template

### Step 1: Enumerate Multi-Step Operations

Find all operations that span multiple transactions:

| Operation | Step 1 (Initiate) | Wait Condition | Step N (Complete) |
|-----------|-------------------|----------------|-------------------|
| {op_name} | {module::initiate_fn}() | {wait_condition} | {module::complete_fn}() |

**Sui-specific multi-step patterns**:
- Unstaking: request_withdraw() -> wait epochs -> complete_withdraw()
- Governance: propose() -> wait voting period -> execute()
- Vesting: create_vest() -> wait lock period -> claim()
- Cooldowns: initiate_action() -> wait cooldown (clock-based) -> finalize_action()

**Time sources on Sui**:
- `clock::timestamp_ms(clock: &Clock)`: Real-time milliseconds. Shared object at `0x6`. Monotonically increasing. Used for time-based delays.
- `tx_context::epoch(ctx: &TxContext)`: Epoch number. Incremented roughly every 24 hours. Used for epoch-based staking/unstaking.

For each multi-step operation:
- What parameters are read/cached at Step 1?
- What parameters are re-read at Step N?
- What parameters are used but NOT re-read at Step N? (stored in user's owned object or shared object field)

### Step 2: Identify Cached Parameters

For each parameter used across steps:

| Parameter | Stored In | Read At Step | Cached? | Admin-Changeable? | Re-Validated At Completion? |
|-----------|----------|-------------|---------|-------------------|----------------------------|
| {param} | Shared config object | initiate() L{N} | YES/NO | YES/NO (requires {CapType}) | YES/NO |
| {delay_param} | User's receipt object | initiate() L{N} | YES (in receipt) | YES/NO | YES/NO |

**Sui caching patterns**:
- Parameter stored in shared config object: read at initiation, may change before completion
- Parameter stored in user's receipt/ticket object (owned): cached at initiation, immutable until completion
- Parameter in dynamic field: may be updated independently of the operation

**Red flags**: Parameter is cached at Step 1 AND changeable via admin capability AND NOT re-validated at Step N.

### Step 3: Model Staleness Impact

For each cached parameter that can become stale:

```
Scenario A: Parameter INCREASES between steps
1. User initiates at Step 1 with param = X (stored in receipt)
2. Admin (via AdminCap) changes param to X + delta in shared config
3. User completes at Step N
4. Impact: {what happens with stale value X when current is X + delta}

Scenario B: Parameter DECREASES between steps
1. User initiates at Step 1 with param = X
2. Admin changes param to X - delta
3. User completes at Step N
4. Impact: {what happens with stale value X when current is X - delta}
```

**BOTH directions are mandatory** -- increase and decrease often have different impacts.

**Sui-specific staleness vectors**:
- Epoch-based operations: If unstaking delay is cached as "epoch + N" and N is changed, the cached deadline may be too early or too late
- Clock-based cooldowns: If cooldown duration changes, users with in-flight operations may bypass or be locked longer
- Fee parameters: If fee rate changes between request and execution, user pays stale rate

**PTB bypass check (CRITICAL)**: Can Steps 1 and N both be executed within a single PTB?
- If YES with time-based waits (`clock::timestamp_ms` comparisons): bypassed -- same Clock timestamp within a PTB
- If YES with epoch-based waits (`tx_context::epoch` comparisons): bypassed -- same epoch within a PTB
- Only hot-potato receipts (zero-ability structs) or consumed/destroyed objects can enforce multi-transaction separation
- If PTB bypass is possible -> escalate severity (time controls are ineffective)

### Step 3b: Update Source Audit
For each parameter updated from an external source:
- Is the source (e.g., oracle, clock, epoch) the correct representation of what this parameter tracks?
- Should this parameter be fixed for a period (e.g., per epoch, per cycle) rather than continuously refreshed?
- Which functions update it? Which functions SHOULD update it? Any mismatch?
- **Sui-specific**: Does the parameter depend on `clock::timestamp_ms` (continuous) vs `tx_context::epoch` (discrete)? Is the choice appropriate?
- **Unit consistency**: Verify all timestamp arithmetic uses consistent units. `clock::timestamp_ms()` returns milliseconds; external sources (Pyth `publish_time`, cross-chain timestamps) typically use seconds. Any comparison or subtraction without ×1000 conversion → FINDING.

### Step 4: Retroactive Application Analysis

For fee/rate parameters that apply to existing state:

| Parameter | Applies To | Retroactive? | Impact |
|-----------|-----------|--------------|--------|
| {fee_param} | {what it affects} | YES/NO | {if retroactive: who is harmed} |

**Pattern**: Fee changes that affect already-accrued rewards or already-initiated operations are retroactive.

**Sui-specific retroactive patterns**:
- Staking reward rate changed -> applies to already-staked positions?
- Fee rate changed -> applies to in-flight withdrawals?
- Slippage tolerance changed -> applies to pending swap requests?

### Step 5: Assess Severity

For each staleness issue:
- **Who is affected?** (single user with pending operation, all users with pending operations, protocol)
- **Is the impact bounded?** (capped by fee range, max delay, etc.)
- **Can it be exploited intentionally?** (admin front-running users, users racing admin changes)
- **Is there a recovery path?** (cancel and re-initiate, admin override)

**Severity factors specific to Sui**:
- Epoch transitions are infrequent (~24h) -- staleness impact per epoch change is bounded
- Clock-based parameters can change at any time -- more exploitable
- Shared object contention may delay admin parameter changes, creating a natural buffer

## Key Questions (must answer all)

1. What multi-step operations exist? (request/claim, deposit/lock/withdraw, propose/vote/execute)
2. For each cached parameter: can admin (via capability) change it between steps?
3. What happens if a delay DECREASES after initiation? (users locked longer than necessary)
4. What happens if a delay INCREASES after initiation? (users can claim too early)
5. Are fees applied retroactively to existing positions or only to new ones?
6. Is there a maximum parameter range (enforced bounds) that limits the staleness impact?

## Common False Positives

- **Immutable parameters**: If the parameter is set at object creation and has no setter function, no staleness
- **Bounded ranges**: If min/max bounds limit the change magnitude, impact may be Low
- **User can cancel and re-initiate**: If users can abort pending operations with new parameters, reduced severity
- **Timelock on parameter changes**: If parameter changes require a delay (e.g., governance proposal), users have time to react
- **Per-operation snapshots**: If each operation stores its own copy of the parameter (in receipt/ticket object), it is isolated from changes

## Instantiation Parameters
```
{CONTRACTS}           -- Move modules to analyze
{MULTI_STEP_OPS}      -- Identified multi-step operations
{CACHED_PARAMS}       -- Parameters cached at initiation
{ADMIN_PARAMS}        -- Admin-changeable parameters (via capability)
{DELAY_PARAMS}        -- Delay/cooldown parameters (clock or epoch based)
{FEE_PARAMS}          -- Fee/rate parameters that may apply retroactively
{CLOCK_USAGE}         -- Functions using clock::timestamp_ms
{EPOCH_USAGE}         -- Functions using tx_context::epoch
```

## Output Schema
| Field | Required | Description |
|-------|----------|-------------|
| multi_step_ops | yes | List of multi-step operations found |
| cached_params | yes | Parameters cached across steps |
| staleness_vectors | yes | How cached params can become stale |
| retroactive_fees | yes | Fees applied retroactively |
| finding | yes | CONFIRMED / REFUTED / CONTESTED |
| evidence | yes | Code locations with line numbers |
| step_execution | yes | Status for each step |

---

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Enumerate Multi-Step Operations | YES | | |
| 2. Identify Cached Parameters | YES | | |
| 3. Model Staleness Impact (both directions) | YES | | |
| 3 (PTB bypass check) | YES | | Can Steps 1+N execute in same PTB? |
| 3b. Update Source Audit | YES | | |
| 4. Retroactive Application Analysis | YES | | |
| 5. Assess Severity | YES | | |

### Cross-Reference Markers

**After Step 2**: If cached parameters are admin-changeable via capability -> MUST complete Step 3 with BOTH increase and decrease scenarios.

**After Step 3 (PTB bypass)**: If PTB bypass is possible -> escalate severity (time controls are ineffective). Only hot-potato receipts enforce multi-transaction separation.

**After Step 4**: Cross-reference with SEMI_TRUSTED_ROLES.md for capability holders that change these parameters -- is the parameter change within or outside the role's stated trust boundary?

## references/sui/token-flow-tracing.md

---
name: "token-flow-tracing"
description: "Trigger Pattern BALANCE_DEPENDENT flag (required) - Inject Into Depth-token-flow, breadth agents"
---

# TOKEN_FLOW_TRACING Skill (Sui)

> **Trigger Pattern**: BALANCE_DEPENDENT flag (required)
> **Inject Into**: Depth-token-flow, breadth agents
> **Purpose**: Trace all Coin<T> and Balance<T> flows through Sui Move protocols to identify accounting desync, unsolicited deposit vectors, type confusion, and token lifecycle issues

For every token the protocol handles:

## 1. Asset Inventory

Enumerate ALL `Coin<T>` and `Balance<T>` types the protocol handles:

| Token Type | Representation | Location (module::struct field) | Owned or Shared? | Entry Functions | Exit Functions |
|------------|---------------|-------------------------------|-------------------|-----------------|----------------|
| {e.g., SUI} | Balance<SUI> | pool::Pool.balance | Shared object | deposit() | withdraw() |
| {e.g., USDC} | Coin<USDC> | (function parameter) | Owned (user) | swap_in() | swap_out() |

**Sui-specific representations**:
- `Coin<T>`: Owned object with `id: UID` and `balance: Balance<T>`. Has `key + store` abilities -- freely transferable to any address via `transfer::public_transfer`. This is the primary unsolicited transfer vector on Sui.
- `Balance<T>`: Value type with `store` ability only (no `key`). Stored inside other objects. Cannot exist as a standalone on-chain object. Must live inside a struct with `key`.
- `TreasuryCap<T>`: Capability to mint/burn. Must be tracked.
- Dynamic fields storing `Balance<T>`: Hidden balance storage -- check `dynamic_field::add/borrow/remove`.

## 2. Token Entry Points

Where can tokens enter the protocol?

| Entry Path | Function | Token Form | Accounting Update | Validated? |
|------------|----------|-----------|-------------------|------------|
| Standard deposit | {module::deposit} | Coin<T> parameter | {state variable updated} | YES/NO |
| PTB coin splitting | (PTB splits user coin) | Coin<T> from split | {same as above?} | N/A |
| Direct transfer | transfer::public_transfer | Coin<T> to address | NONE -- creates new owned object | NO |
| Balance merge | balance::join | Balance<T> | {state variable updated?} | YES/NO |
| Mint | coin::from_balance / coin::mint | TreasuryCap | {supply tracking} | YES/NO |
| Side-effect receipt | {external call returns coin} | Coin<T> | {handled?} | YES/NO |

**Sui-specific entry analysis**:
- `coin::into_balance(coin)` converts Coin<T> to Balance<T> -- does the protocol track this conversion?
- `coin::split(&mut coin, amount, ctx)` creates a new Coin<T> -- does the protocol validate the split amount?
- Can users pass zero-value coins? (`coin::zero<T>(ctx)`)

## 3. Token Exit Points

Where can tokens leave the protocol?

| Exit Path | Function | Token Form | Accounting Update | Authorized? |
|-----------|----------|-----------|-------------------|-------------|
| Standard withdraw | {module::withdraw} | Coin<T> returned | {state variable decremented} | {access check} |
| Transfer out | transfer::public_transfer | Coin<T> | {accounting updated?} | {access check} |
| Balance extraction | balance::split | Balance<T> | {state variable decremented?} | {access check} |
| Burn | coin::burn / balance::decrease_supply | TreasuryCap | {supply tracking} | {cap holder} |
| Fee distribution | {fee function} | Coin<T> or Balance<T> | {fee accounting} | {access check} |
| Emergency withdraw | {emergency function} | Coin<T> | {does it clear ALL state?} | {admin cap?} |

For each exit: does the tracked balance decrease BEFORE or AFTER the actual balance extraction?
For each transfer/withdrawal: can the source be underfunded at execution time? (funds deployed externally, locked, or lent out → transfer aborts)
Check for:
- `balance::split` before state update -> can the function abort between split and update?
- State update before `balance::split` -> can state be inconsistent if split aborts?

### 3b. Self-Transfer Accounting
For each transfer function: can the sender and recipient be the same address/object?
If YES: does a self-transfer update accounting state (fees credited, rewards claimed, snapshots updated, share ratios changed) without net token movement? Flag as FINDING.

## 4. Balance Tracking and Desync Analysis

For each token in the protocol:

| Token | Internal Tracking Variable | Actual Balance Source | Can They Desync? | Desync Vector |
|-------|---------------------------|---------------------|-----------------|---------------|
| {token} | {e.g., pool.total_deposited} | balance::value(&pool.balance) | YES/NO | {how} |

**Red flags**:
- Exchange rate calculations using `balance::value()` directly instead of tracked internal variable
- No reconciliation mechanism between tracked and actual balance
- Accounting variables updated in a different function than the balance transfer
- `balance::join(&mut pool.balance, deposit_balance)` without incrementing tracked counter

**Sui-specific desync vectors**:
- `balance::join` into a shared object's Balance<T> without updating the tracking variable
- Dynamic field operations that add/remove Balance<T> without pool-level accounting
- Multiple shared objects holding the same token type with aggregate accounting errors

## 5. Unsolicited Deposit Analysis

Can tokens be added to the protocol's balance without going through deposit logic?

**Sui object model considerations**:
- **Owned objects**: Only the owner can access. Sending `Coin<T>` via `transfer::public_transfer` to a shared object's address creates a NEW owned object at that address -- it does NOT add to the shared object's `Balance<T>`. The protocol would need to explicitly receive and merge it.
- **Shared objects**: Anyone can call functions on shared objects, but cannot directly modify their `Balance<T>` fields without going through the module's public API.
- **However**: If the module exposes a public function that accepts `Coin<T>` and calls `balance::join` without proper accounting, this IS a donation vector.

| Donation Path | Possible? | Changes Protocol Balance? | Breaks Accounting? | Impact |
|---------------|-----------|--------------------------|-------------------|--------|
| transfer::public_transfer to pool address | YES (creates owned obj) | NO (not auto-merged) | NO (unless protocol sweeps) | {analysis} |
| Public function accepting Coin<T> without accounting | {YES/NO} | YES | YES | {analysis} |
| Dynamic field injection | {YES/NO -- needs module API} | {YES/NO} | {YES/NO} | {analysis} |
| Reward/fee distribution to protocol address | {YES/NO} | {YES/NO} | {YES/NO} | {analysis} |

### 5b. Unsolicited Transfer Matrix (All Token Types)

For EVERY external token type the protocol holds, queries, or receives as side effects -- not just the protocol's primary token:

| Token Type | Can Be Sent to Protocol? | Changes Protocol Accounting? | Blocks Operations? | Triggers Side Effects? |
|------------|--------------------------|-----------------------------|--------------------|----------------------|
| {token_a} | YES/NO | YES/NO | YES/NO | YES/NO |

**RULE**: If ANY token type can enter the protocol's balance AND affects state -> analyze each consequence:
- Accounting impact: Does tracked vs actual balance diverge?
- Iteration impact: Does the protocol iterate over sources of this token? (gas DoS vector via object count)
- Operation blocking: Does non-zero balance of this token prevent admin operations?
- Side effect chain: Does receiving this token trigger further side effects?

## 6. Token Type Confusion

Can the wrong `Coin<T>` type be passed to protocol functions?

| Function | Expected Type Parameter | Validated? | What if Wrong Type? |
|----------|------------------------|-----------|---------------------|
| {function} | Coin<USDC> | {by Move type system / runtime check} | {impact} |

**Sui Move type safety**: Move's type system provides strong static guarantees -- `Coin<USDC>` and `Coin<SUI>` are different types at compile time. However:
- Generic functions `fun deposit<T>(coin: Coin<T>)` accept ANY `Coin<T>` -- is `T` validated?
- Does the protocol check `T` against an allowed list? (e.g., `assert!(type_name::get<T>() == allowed_type)`)
- Can an attacker create a custom token type and pass `Coin<ATTACKER_TOKEN>` to a generic function?
- For pools with multiple token types: can the type parameters be swapped? (e.g., `Pool<A, B>` called with `Coin<B>` where `Coin<A>` expected)

## 7. Coin Splitting and Merging

Analyze `coin::split()` and `coin::join()` / `balance::split()` and `balance::join()` operations:

| Operation | Location | Amount Source | Validated? | Edge Cases |
|-----------|----------|-------------|-----------|------------|
| coin::split(&mut coin, amount, ctx) | {location} | {user input / computed} | {amount <= coin.value?} | {amount = 0? amount = full value?} |
| coin::join(&mut coin_a, coin_b) | {location} | {coin_b.value} | {overflow check?} | {coin_b is zero?} |
| balance::split(&mut bal, amount) | {location} | {computed} | {amount <= bal.value?} | {amount = 0?} |
| balance::join(&mut bal_a, bal_b) | {location} | {bal_b.value} | {overflow check?} | {bal_b is zero?} |

**Check**:
- Off-by-one errors in split amounts
- Dust remaining after splits (tiny amounts that cannot be withdrawn)
- Zero-value splits: `coin::split(&mut coin, 0, ctx)` creates a zero-value coin -- does the protocol handle this?
- Full-value splits: splitting the entire balance leaves a zero-value coin/balance in place

## 8. Zero-Value Operations

What happens with zero-value tokens?

| Operation | Zero Input Behavior | Impact |
|-----------|-------------------|--------|
| deposit(coin::zero<T>()) | {aborts / succeeds / mints zero shares} | {accounting impact} |
| withdraw(0) | {aborts / succeeds / burns zero shares} | {accounting impact} |
| swap(coin::zero<T>()) | {aborts / succeeds} | {state change without value?} |
| claim_rewards() when rewards = 0 | {aborts / succeeds} | {side effects?} |

**Check**: Can zero-value operations be used to:
- Trigger state changes without economic commitment?
- Reset cooldown timers?
- Increment counters or advance epochs?
- Create empty objects that consume storage?

## 9. Cross-Token Interactions

For protocols with multiple token types:

| Interaction | Token A | Token B | Dependency | Impact |
|-------------|---------|---------|-----------|--------|
| Exchange rate | {A type} | {B type} | {A balance affects B's rate?} | {if A manipulated, B price changes?} |
| Collateral/debt | {collateral type} | {debt type} | {collateral value gates borrowing} | {if collateral inflated, excess borrowing} |
| LP composition | {A type} | {B type} | {ratio determines share value} | {imbalance vector} |

- Can operations on Token A affect Token B's accounting?
- Are there exchange rate dependencies between tokens?
- Can withdrawing Token A affect availability of Token B?

## Finding Template

```markdown
**ID**: [TF-N]
**Severity**: [based on fund impact]
**Step Execution**: check1,2,3,4,5,6,7,8,9 | x(reasons) | ?(uncertain)
**Rules Applied**: [R1:check, R11:check, R4:check, R10:check]
**Location**: module::function:LineN
**Title**: [Token type] can enter/exit via [path] without [expected accounting update]
**Description**: [Trace the token flow and where it diverges from expected]
**Impact**: [What breaks: exchange rates, user balances, protocol insolvency]
```

## Instantiation Parameters
```
{CONTRACTS}           -- Move modules to analyze
{TOKEN_TYPES}         -- Coin<T>/Balance<T> types handled
{SHARED_OBJECTS}      -- Shared objects holding balances
{ENTRY_FUNCTIONS}     -- Token deposit/entry functions
{EXIT_FUNCTIONS}      -- Token withdraw/exit functions
{GENERIC_FUNCTIONS}   -- Functions with type parameter <T>
```

## Output Schema
| Field | Required | Description |
|-------|----------|-------------|
| asset_inventory | yes | All Coin<T> and Balance<T> types |
| entry_points | yes | All token entry paths |
| exit_points | yes | All token exit paths |
| balance_tracking | yes | Internal vs actual balance analysis |
| unsolicited_analysis | yes | Donation/unsolicited deposit vectors |
| type_confusion | yes | Type parameter validation |
| finding | yes | CONFIRMED / REFUTED / CONTESTED |
| evidence | yes | Code locations with line numbers |
| step_execution | yes | Status for each step |

---

## Step Execution Checklist (MANDATORY)

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Asset Inventory | YES | check/x/? | |
| 2. Token Entry Points | YES | check/x/? | |
| 3. Token Exit Points | YES | check/x/? | |
| 4. Balance Tracking and Desync | YES | check/x/? | |
| 5. Unsolicited Deposit Analysis | YES | check/x/? | |
| 5b. Unsolicited Transfer Matrix (All Types) | **YES** | check/x/? | **MANDATORY** -- never skip |
| 6. Token Type Confusion | YES | check/x/? | |
| 7. Coin Splitting and Merging | YES | check/x/? | |
| 8. Zero-Value Operations | YES | check/x/? | |
| 9. Cross-Token Interactions | IF multi-token | check/x(N/A)/? | |

### Cross-Reference Markers

**After Section 5** (Unsolicited Deposit Analysis):
- IF donation vectors found -> **MUST check impact on exchange rates in Section 4**
- IF protocol has generic functions -> **MUST complete Section 6**

**After Section 7** (Coin Splitting and Merging):
- IF dust amounts possible -> **MUST check zero-value impact in Section 8**
- Cross-reference with ZERO_STATE_RETURN for residual balance implications

**After Section 8** (Zero-Value Operations):
- IF zero-value operations cause state changes -> FINDING (minimum Low)
- Document: "Zero-value [operation] triggers [state change] without economic commitment"

## references/sui/type-safety.md

---
name: "type-safety"
description: "Trigger Pattern Always (Sui Move) -- generic type exploitation - Inject Into Breadth agents, depth-state-trace"
---

# TYPE_SAFETY Skill

> **Trigger Pattern**: Always (Sui Move) -- generic type exploitation
> **Inject Into**: Breadth agents, depth-state-trace

For every generic function and parameterized type in the protocol:

**STEP PRIORITY**: Steps 4 (OTW Analysis) and 6 (Coin/Balance Type Safety) are where HIGH/CRITICAL severity findings most commonly hide. Do NOT rush these steps. If constrained, skip conditional sections before skipping 4 or 6.

## 1. Generic Function Inventory

Enumerate ALL functions with type parameters across all modules:

| Module | Function | Type Params | Constraints | Public? | Phantom? | Notes |
|--------|----------|-------------|-------------|---------|----------|-------|
| {mod} | {func} | `<T>`, `<T: store>`, etc. | {ability constraints} | YES/NO | YES/NO | {context} |

**Sui type parameter semantics**:
- `<T>` -- unconstrained. T can be ANY type. Maximally permissive.
- `<T: key + store>` -- T must be an object that can be freely transferred.
- `<T: drop>` -- T can be discarded. Often used for witness patterns.
- `<phantom T>` -- T is not used at runtime, only for type distinction (e.g., `Coin<phantom T>`). No ability constraints enforced on phantom params at the struct level.

## 2. Type Parameter Constraint Analysis

For each generic function, verify constraints are sufficient:

### 2a. Under-Constrained Parameters

| Function | Param | Constraint | Actually Used As | Sufficient? |
|----------|-------|-----------|-----------------|-------------|
| {func} | `T` | none | stored in dynamic field (needs `store`) | **NO** |
| {func} | `T` | `store` | used as `Coin<T>` balance | **NO** -- needs further check |

**Check**: For each type parameter, trace how it is actually used in the function body:
- If stored in a struct field -> needs at minimum `store`
- If used as an object -> needs `key`
- If discarded without explicit destruction -> needs `drop`
- If the function works correctly with ANY type -> unconstrained is correct

### 2b. Over-Constrained Parameters (Info-Level)

| Function | Param | Constraint | Actually Needed | Over-Constrained? |
|----------|-------|-----------|----------------|-------------------|
| {func} | `T` | `key + store + drop` | only `store` | YES -- limits usability |

**Note**: Over-constraining is not a security issue but limits composability. Document as Informational.

### 2c. Phantom Type Correctness

| Struct | Phantom Param | Used in Runtime Logic? | Type-Level Distinction Sound? |
|--------|--------------|----------------------|------------------------------|
| {struct} | `phantom T` | YES (BUG) / NO | YES/NO |

**Rule**: Phantom type parameters MUST NOT be used in runtime field types (non-phantom positions). If a phantom param is used in a non-phantom position, the compiler rejects it. But check: is the phantom param providing meaningful type distinction, or can an attacker substitute any type?

## 3. Type Witness Pattern Audit

Identify all witness patterns (structs used for one-time authorization):

| Witness Type | Module | Created In | Consumed In | Abilities | Singleton? |
|-------------|--------|------------|-------------|-----------|-----------|
| {name} | {mod} | {function} | {function} | `drop` only / none | YES/NO |

**Witness security checks**:
- [ ] Is the witness created ONLY in the intended function? (Check all `new` / constructor paths.)
- [ ] Is the witness consumed (dropped or destructured) immediately after use?
- [ ] Can the witness be stored? (If it has `store` -> it can persist beyond its intended scope -> FINDING.)
- [ ] Can the witness be copied? (If it has `copy` -> it can be reused -> FINDING.)
- [ ] Is the witness type public? (If the struct is public, external modules can potentially construct it if they can satisfy its fields.)

### 3a. Witness Forgery Check

For each witness type:
```
Can an attacker construct this witness type?
1. Is the struct definition public (`public struct`)? -> External modules CAN create instances if fields are accessible
2. Does the struct have fields? -> If no fields (unit struct), only the defining module can create it
3. Are all field types accessible to external modules? -> If yes, external construction possible
4. Is construction gated by `init` or capability? -> Check the gate
```

**Rule**: A witness with a public struct definition and publicly-accessible field types is forgeable from external modules. This is a CRITICAL finding if the witness gates value creation (coin minting, capability issuance, etc.).

## 4. One-Time Witness (OTW) Analysis

Identify all OTW patterns:

| Module | OTW Type | `init` Signature | OTW Consumed? | Package Upgrade Safe? |
|--------|----------|-----------------|---------------|----------------------|
| {mod} | `{MODULE_NAME}` (uppercase) | `init(otw: MODULE_NAME, ctx: &mut TxContext)` | YES/NO | YES/NO |

**Sui OTW rules**:
- OTW type name MUST match the module name in UPPERCASE.
- OTW MUST have `drop` ability (and typically no other abilities).
- OTW is automatically created by the Sui runtime and passed to `init` on module publish.
- OTW MUST be consumed in `init` (typically passed to `coin::create_currency` or similar).

**Security checks**:
- [ ] Does the OTW have ONLY `drop` ability? If it has `copy` -> can be duplicated (should be impossible due to Sui's OTW rules, but verify). If it has `store` -> can be persisted past `init` -> FINDING.
- [ ] Is the OTW consumed (used as a move value, not just referenced) in `init`? If stored instead of consumed -> it can be reused.
- [ ] **Package upgrade**: Sui package upgrades do NOT re-run `init`. Is the protocol relying on `init` for setup that should be repeatable? If the module uses OTW to create a `TreasuryCap` or `Publisher`, those are one-time-only.
- [ ] Can the OTW check be bypassed? Functions that accept `T: drop` as a witness without verifying it is the actual OTW type -> can be called with any `drop`-able type.

### 4a. OTW Verification Pattern

Check if the protocol uses `sui::types::is_one_time_witness<T>()` to verify OTW:

| Function | Accepts Generic Witness? | OTW Verification? | Bypass Possible? |
|----------|------------------------|-------------------|-----------------|
| {func} | `<T: drop>(witness: T)` | `is_one_time_witness(&witness)` / NO | YES/NO |

**Rule**: Any public function that accepts a generic witness `<T: drop>` without calling `is_one_time_witness` can be called with any droppable type, not just the actual OTW. This is a HIGH finding if the function creates currencies, capabilities, or other privileged objects.

## 5. Generic Type Confusion Attacks

Model attacks where an attacker substitutes an unexpected type:

### 5a. Function-Level Type Confusion

For each public generic function:
```
Can an attacker call function<MaliciousType>() where the protocol expects function<ExpectedType>()?
1. What type does the protocol intend?
2. What constraints prevent substitution?
3. What happens if a different type is passed?
```

| Function | Expected Type | Constraint | Substitute Possible? | Impact |
|----------|--------------|-----------|---------------------|--------|
| {func} | `SUI` | none (just `<T>`) | YES -- any type | {impact} |
| {func} | `USDC` | `<T: store>` | YES -- any `store` type | {impact} |
| {func} | specific coin | runtime check on `CoinMetadata` | NO | N/A |

### 5b. Struct-Level Type Confusion

For each generic struct:
```
Pool<T> { balance: Balance<T>, ... }

Can an attacker create Pool<FakeToken> and interact with Pool<RealToken>'s functions?
```

**Check**: Does the protocol use type parameters to distinguish pools/vaults? If yes:
- Are operations on `Pool<A>` and `Pool<B>` fully isolated?
- Can an attacker drain `Pool<A>` by exploiting `Pool<FakeA>`?
- Is there a registry/mapping that validates the type parameter? (e.g., `Table<TypeName, PoolConfig>`)

## 6. Coin/Balance Type Safety

Specific analysis for `Coin<T>` and `Balance<T>` patterns:

### 6a. Coin Type Verification

| Function | Accepts `Coin<T>` | T Verified? | Verification Method | Bypass? |
|----------|-------------------|-------------|--------------------|---------:|
| {func} | YES | YES/NO | {method or NONE} | YES/NO |

**Sui Coin safety model**:
- `Coin<T>` is parameterized by the coin type `T`.
- Creating `Coin<T>` requires a `TreasuryCap<T>`, which is created via OTW in `init`.
- An attacker CANNOT create `Coin<SUI>` because they don't have `TreasuryCap<SUI>`.
- But an attacker CAN create `Coin<ATTACKER_TOKEN>` and pass it to a function expecting `Coin<T>` if T is generic.

**Checks**:
- [ ] Do functions that handle coins use specific types (`Coin<SUI>`) or generic (`Coin<T>`)?
- [ ] If generic: is T validated against an allowed set?
- [ ] Can `Balance<FakeToken>` be joined with `Balance<RealToken>`? (NO -- type system prevents this. But verify no unsafe transmutation exists.)
- [ ] Are `Coin` split/merge operations type-safe? (`coin::split` preserves T.)

### 6b. Balance Accounting Type Safety

| Operation | Input Type | Output Type | Type Preserved? | Accounting Impact |
|-----------|-----------|-------------|----------------|------------------|
| deposit | `Coin<T>` | `Balance<T>` (internal) | YES/NO | {impact if mismatch} |
| withdraw | `Balance<T>` (internal) | `Coin<T>` | YES/NO | {impact if mismatch} |
| swap | `Coin<A>` -> `Coin<B>` | both types | YES/NO | {impact if mismatch} |

**Check**: At every point where `Balance<T>` is converted to/from `Coin<T>`, is the type parameter `T` consistent? The compiler enforces this for concrete types, but for generic functions operating on `Balance<T>`, trace that T remains the same throughout the flow.

## 7. Publisher and Package Authority

Analyze `Publisher` object usage:

| Module | Publisher Created? | Used For | Stored/Shared? | Transfer Restricted? |
|--------|-------------------|----------|---------------|---------------------|
| {mod} | YES/NO | {display, transfer policy, etc.} | {how stored} | YES/NO |

**Security checks**:
- `Publisher` proves package authorship. Functions that accept `&Publisher` trust the caller is the package publisher.
- [ ] Is `Publisher` stored in a shared object (accessible to anyone with the right reference)?
- [ ] Can `Publisher` be transferred to a malicious actor?
- [ ] Are there functions that accept `Publisher` from external callers? (These trust the caller is a publisher.)
- [ ] **Package upgrade**: After an upgrade, the original `Publisher` remains valid. Does the protocol account for this?

## Finding Template

```markdown
**ID**: [TS-N]
**Severity**: [CRITICAL if coin forgery/capability bypass, HIGH if type confusion with value, MEDIUM if witness issue]
**Step Execution**: check1,2,3,4,5,6,7 | X(reasons) | ?(uncertain)
**Rules Applied**: [R4:Y, R5:Y, R10:Y, ...]
**Depth Evidence**: [VARIATION:T=FakeToken vs T=SUI], [TRACE:generic_fn<Attacker>->state_corruption]
**Location**: module::function
**Title**: [Type safety issue] in [function] enables [attack/bypass]
**Description**: [Specific type parameter exploitation path with concrete substitute type]
**Impact**: [Unauthorized coin minting, capability forgery, pool drainage, accounting corruption]
```

---

## Step Execution Checklist (MANDATORY)

> **CRITICAL**: You MUST report completion status for ALL sections. Findings with incomplete sections will be flagged for depth review.

| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Generic Function Inventory | YES | Y/X/? | All modules |
| 2. Type Parameter Constraint Analysis | YES | Y/X/? | |
| 2c. Phantom Type Correctness | IF phantom params | Y/X(N/A)/? | |
| 3. Type Witness Pattern Audit | IF witness patterns | Y/X(N/A)/? | |
| 3a. Witness Forgery Check | IF witness patterns | Y/X(N/A)/? | |
| 4. OTW Analysis | IF `init` with witness | Y/X(N/A)/? | **HIGH PRIORITY** |
| 4a. OTW Verification Pattern | IF generic witness functions | Y/X(N/A)/? | |
| 5. Generic Type Confusion Attacks | YES | Y/X/? | |
| 5b. Struct-Level Type Confusion | IF generic structs | Y/X(N/A)/? | |
| 6. Coin/Balance Type Safety | IF Coin/Balance used | Y/X(N/A)/? | **HIGH PRIORITY** |
| 6b. Balance Accounting Type Safety | IF Balance used | Y/X(N/A)/? | |
| 7. Publisher and Package Authority | IF Publisher used | Y/X(N/A)/? | |

### Cross-Reference Markers

**After Section 3** (Witness Pattern Audit):
- Cross-reference with ABILITY_ANALYSIS Section 4 (Capability Pattern Audit) -- witnesses often gate capabilities
- IF witness is forgeable -> escalate to CRITICAL and cross-reference all functions that accept it

**After Section 4** (OTW Analysis):
- Cross-reference with ABILITY_ANALYSIS Section 8 (Module Initializer Audit) -- OTW is consumed in `init`
- IF OTW not consumed -> check if `TreasuryCap` or `Publisher` can be created multiple times

**After Section 6** (Coin/Balance Type Safety):
- Cross-reference with TOKEN_FLOW_TRACING for multi-token accounting
- IF generic coin functions lack type verification -> model type confusion attack with concrete substitute

**After Section 7** (Publisher Authority):
- IF Publisher stored in shared object -> cross-reference with SEMI_TRUSTED_ROLES (who can access it?)
- IF package upgrade possible -> document Publisher persistence across upgrades

## references/sui/verification-protocol.md

---
name: "verification-protocol"
description: "Trigger Pattern Always (used by all verifier agents) - Inject Into security-verifier agents (Phase 5)"
---

# Verification Protocol (Sui Move)

> **Trigger Pattern**: Always (used by all verifier agents)
> **Inject Into**: security-verifier agents (Phase 5)
> **Purpose**: Prove hypotheses TRUE or FALSE using Sui Move test framework with `test_scenario` PoC code.

---

## Evidence Source Tracking (MANDATORY)

> **CRITICAL**: For EVERY piece of evidence used in verification, you MUST tag its source.
> Evidence from mocks or unverified external packages CANNOT support a REFUTED verdict.

### Evidence Source Tags

| Tag | Meaning | Valid for REFUTED? |
|-----|---------|-------------------|
| [PROD-ONCHAIN] | Production Sui object data (via Sui Explorer or RPC) | YES |
| [PROD-SOURCE] | Verified source from Sui Explorer / published package | YES |
| [PROD-PUBLISHED] | Test against published package bytecode | YES |
| [CODE] | Audited codebase (in-scope source) | YES |
| [MOCK] | Mock/test modules or objects | **NO** |
| [EXT-UNV] | External, unverified package behavior | **NO** |
| [DOC] | Documentation/spec only | **NO** (needs verification) |

### Evidence Audit Table (REQUIRED in every verification output)

Before ANY verdict, fill this table:

```markdown
### Evidence Audit
| Claim | Evidence Source | Tag | Valid for REFUTED? |
|-------|-----------------|-----|-------------------|
| "External package returns X" | Mock module | [MOCK] | NO |
| "Object ownership is Y" | sources/module.move:123 | [CODE] | YES |
| "Shared object state is Z" | Sui Explorer object view | [PROD-ONCHAIN] | YES |
```

### Mock Rejection Rule

**AUTOMATIC OVERRIDE**: If ANY evidence supporting REFUTED has tag [MOCK] or [EXT-UNV]:
- CANNOT return REFUTED
- MUST return CONTESTED
- Triggers production verification

**Example**:
```markdown
## Verdict: REFUTED -> CONTESTED (mock evidence override)

### Evidence Audit
| Claim | Source | Tag | Valid? |
|-------|--------|-----|--------|
| "External module validates input" | test_helper.move:45 | [MOCK] | NO |

**Override reason**: REFUTED verdict relies on mock behavior at test_helper.move:45.
Production package behavior is UNVERIFIED. Must fetch published package source.
```

---

## Pre-Verification Understanding

Before writing ANY test code, you MUST answer:

### Question 1: What is the EXACT bug?
```
NOT: "Object ownership is wrong"
NOT: "Access control is missing"
NOT: "State is inconsistent"

YES: "Function [X] in module [Y] accepts shared object [Z] as `&mut` without
      verifying caller holds [CapabilityType], allowing any address to mutate
      field [W] at line [N]"
```

### Question 2: What OBSERVABLE difference proves it?
```
NOT: "State changed"
NOT: "Object was modified"

YES: "Before exploit: pool.total_supply = 1000, attacker_balance = 0
      After exploit: pool.total_supply = 1000, attacker_balance = 500
      Expected: transaction should have aborted with ENotAuthorized"
```

### Question 3: What is the EXACT assertion?
```
NOT: assert!(exploit_worked, 0)

YES: assert!(coin::value(&stolen_coin) > 0, ERR_EXPLOIT_FAILED)
 OR: // Transaction should abort -- if it succeeds, the bug exists
 OR: assert!(state_after.field != state_before.field, ERR_STATE_UNCHANGED)
```

**If you cannot answer all three -> ASK FOR CLARIFICATION**

---

## Pre-PoC Feasibility Gates (MANDATORY)

Before writing test code, verify these two gates. If either FAILS, adjust the hypothesis.

### Gate F1: Reachability
Trace a call path from a permissionless entry point to the vulnerable code.

- [ ] Entry point identified (public/external/entry function)
- [ ] Call path traced through intermediary functions
- [ ] All access checks on the path are passable by the attacker profile

If NO entry point reaches the vulnerable code → UNREACHABLE → FALSE_POSITIVE.
If reachable only through a restricted path → document the restriction, adjust likelihood.

### Gate F2: Math Bounds
Substitute real-world value domains into the expression that triggers the bug.

- [ ] Parameter domains identified (token decimals, max supply, TVL range, fee range, time bounds)
- [ ] Expression evaluated at worst-case feasible inputs
- [ ] Result crosses the bug threshold

If the bug requires values outside feasible domains → INFEASIBLE → FALSE_POSITIVE.
If feasible only at extreme but realistic parameters → document the threshold, proceed with adjusted severity.

**Both gates PASS → proceed to PoC. Either gate FAILS → document and stop.**

---


## Test File Templates

> **See [`templates.md`](references/templates.md)** in this directory for all Sui Move test templates (Templates 1-6: shared object mutation, capability theft, dynamic fields, object wrapping, PTB exploit, concurrent access).

## Interpreting Results

### Test PASSES -> Bug CONFIRMED
The assertion that "proves the bug" succeeded.

### Test FAILS -> Check Why

| Failure | Meaning | Action |
|---------|---------|--------|
| Abort with error code | Function validation rejected the action | Check if rejection IS the bug or a fix |
| `test_scenario::take_from_sender` fails | Object not at expected address | Check transfer logic in setup |
| `test_scenario::take_shared` fails | Shared object not published | Check initialization creates shared objects |
| Type mismatch | Wrong object type taken from scenario | Fix type parameters |
| Arithmetic abort (overflow/underflow) | Math operation failed | Check if this IS the bug or setup error |
| Borrow checker error (compile) | Cannot borrow object mutably | Restructure test to respect Move borrow rules |

---

## Iteration Protocol

**Attempt 1:** Direct implementation of test strategy from hypothesis.

**Attempt 2:** Adjust parameters:
- Different coin amounts (larger/smaller, edge values like 0, 1, u64::MAX)
- Different transaction ordering (swap next_tx blocks)
- Different actor addresses
- Different object states (empty pool, full pool, single-user, multi-user)

**Attempt 3:** Re-examine assumptions:
- Are shared objects properly published in setup?
- Are capability objects at the right addresses?
- Is the module's initialization complete (all shared objects created)?
- Are type parameters correct (generic type instantiation)?
- Does the function require a `Clock` or `TxContext` argument not provided?

**After 5 attempts:** If still fails -> FALSE_POSITIVE with documented reasoning.

---

## Severity Determination

### CRITICAL
- Direct fund theft (Coin drain from shared pools)
- Unauthorized admin capability acquisition
- Arbitrary package upgrade (if upgrade cap compromised and no timelock)
- No special prerequisites needed
- Attacker profits significantly

### HIGH
- Fund loss with specific setup (object pre-creation, ordering dependency)
- Broken core functionality (deposits, withdrawals, swaps, liquidations)
- Shared object state corruption affecting all users
- Significant TVL at risk

### MEDIUM
- Limited fund loss under specific conditions
- Object state corruption (non-fund data)
- Edge cases with real impact at design limits
- Dynamic field pollution affecting protocol behavior
- Moderate value at risk

### LOW
- Negligible direct impact
- Extreme edge cases only
- Admin-controlled risk (with multisig governance)
- View function / event emission issues
- Stranded non-value objects

---

## Exchange Rate Finding Severity (MANDATORY)

> **CRITICAL**: Before assigning severity to ANY finding affecting share/asset ratios or exchange rates, you MUST complete this quantitative analysis.

### Required Quantitative Analysis

For findings affecting exchange rates, fill in this table:

| Metric | Value | Source |
|--------|-------|--------|
| Protocol TVL | [X SUI or USD] | Production or documented estimate |
| Attack cost | [Y] | Calculated from attack steps (gas, tokens, opportunity) |
| Attacker profit | [Z] | Calculated (extraction - cost) |
| Victim loss per user | [W] | Calculated per affected user |
| Affected user count | [N] | one / some / all |
| Profit ratio | [Z/Y] | Attacker profit / attack cost |

### Severity Calculation

**Step 1**: Calculate total impact = W * N (victim loss * affected users)
**Step 2**: Calculate profitability = Z/Y (attacker profit / cost)
**Step 3**: Apply severity matrix:

| Total Impact | Profitability > 2x | Profitability 1-2x | Profitability < 1x |
|--------------|-------------------|-------------------|-------------------|
| > $100,000 | CRITICAL | HIGH | HIGH |
| $10,000 - $100,000 | HIGH | HIGH | MEDIUM |
| $1,000 - $10,000 | HIGH | MEDIUM | MEDIUM |
| < $1,000 | MEDIUM | LOW | LOW |

### What NOT to Do
- "This enables extraction" (qualitative, no numbers)
- "Attacker can profit significantly" (undefined)
- "Loss of funds possible" (unquantified)

### What TO Do
- "Attacker profits 500,000 SUI ($500,000) from 1,000 SUI ($1,000) investment"
- "Each victim loses up to 2% of deposit value, affecting all pool users"
- "Total extractable value: $500,000 with 500x profit ratio -> CRITICAL"

---

## Design Flaw Severity Escalation

When a finding is classified as a "design flaw" rather than an exploit, apply this escalation check:

| Criterion | YES/NO |
|-----------|--------|
| Risk-free for the attacker (no capital at risk, or attacker profits even if partial) | |
| Repeatable (can be executed on every occurrence of a triggering event) | |
| Scales with protocol usage (impact grows with TVL, user count, or time) | |
| No mitigation without code change (off-chain monitoring cannot prevent, only detect) | |

**If ALL 4 criteria are YES**: Severity floor = MEDIUM (cannot be rated LOW or Informational)
**If 3 of 4 criteria are YES**: Recheck -- the remaining criterion may not actually block the attack at scale

---



---

> **Advanced Protocol Reference**: See [`advanced.md`](references/advanced.md) for RAG queries, RAG confidence override, chain hypothesis protection, Sui-specific testing considerations, dual-perspective verification, realistic parameter validation, anti-downgrade guard, new observations, error trace output, and bidirectional role analysis.

## Output Format

### CONFIRMED

```markdown
## Verdict: CONFIRMED

### Bug Mechanism Verified
{Explain what the test_scenario test proves in 2-3 sentences}

### Test Code
{Full Move test function}

### Test Output
{Relevant assertions and logged values from `sui move test`}

### Key Evidence
| Metric | Value |
|--------|-------|
| Before | {value} |
| After | {value} |
| Expected | {value} |
| Difference | {calculation} |

### Evidence Audit
| Claim | Evidence Source | Tag | Valid for REFUTED? |
|-------|-----------------|-----|-------------------|

### RAG Evidence
- **Attack Vectors Consulted**: [list]
- **Similar Exploits Found**: [count]
- **Historical Precedent**: [description]

### Severity: {LEVEL}
{Justification in 1-2 sentences}
```

### FALSE_POSITIVE

```markdown
## Verdict: FALSE_POSITIVE

### Attempts Made

**Attempt 1:**
- Approach: {description}
- Result: {what happened -- include abort codes}
- Learning: {insight}

**Attempt 2:**
- Approach: {description}
- Result: {what happened}
- Learning: {insight}

**Attempt 3:**
- Approach: {description}
- Result: {what happened}
- Learning: {insight}

### Evidence Audit
| Claim | Evidence Source | Tag | Valid for REFUTED? |
|-------|-----------------|-----|-------------------|

### Why It Is Not a Bug
{Explain the actual behavior and why hypothesis was wrong in 2-3 sentences}

### Error Trace
- **Failure Type**: {type}
- **Location**: {location}
- **Error Code**: {code}
- **State at Failure**: {state}
- **Investigation Question**: {question}
```

### CONTESTED

```markdown
## Verdict: CONTESTED

### Evidence Status
| Checkpoint | Status | Details |
|------------|--------|---------|
| External package behavior verified against PRODUCTION | YES/NO | {details} |
| All entry functions checked | YES/NO | {details} |
| Object ownership model verified | YES/NO | {details} |
| Shared object access control confirmed | YES/NO | {details} |

### Evidence Audit
| Claim | Evidence Source | Tag | Valid for REFUTED? |
|-------|-----------------|-----|-------------------|

### Why This Cannot Be REFUTED
{Explain what evidence is missing to definitively rule out the bug}

### Escalation Required
- [ ] Fetch published package source for {external dep}
- [ ] Dump production object state for {object}
- [ ] Check additional entry function paths: {list}

### Error Trace
- **Failure Type**: {type}
- **Location**: {location}
- **Error Code**: {code}
- **State at Failure**: {state}
- **Investigation Question**: {question}
```

---

## Insufficient Evidence (HALT CONDITIONS)

Before marking REFUTED, check ALL boxes:
- [ ] External package behavior verified against PRODUCTION (not mock)
- [ ] Attack path checked on ALL public entry functions that access the same shared objects
- [ ] Profit calculated with attacker HOLDING tokens (not just transferring in)
- [ ] Missing precondition documented (type: STATE / ACCESS / TIMING / EXTERNAL / BALANCE)
- [ ] Searched other findings for matching postconditions (chain analysis integration)
- [ ] Object ownership verified in source (not assumed from naming)
- [ ] Capability access control verified for ALL shared object mutation paths
- [ ] Dynamic field access patterns verified (correct key types, no collisions)

### Evidence That Does NOT Count
- "Mock module shows X" -- mocks are not production behavior
- "Standard Coin<T>" -- may be wrapped in custom module with hooks/restrictions
- "Attacker loses by sending coins" -- may profit via position held in pool
- "Function is `public(package)`" -- may be callable via CPI from another module in the same package
- "Requires AdminCap" -- AdminCap may have `store` ability and be transferable
- "Attacker cannot acquire X" -- another finding may CREATE this condition
- "Object is owned by admin" -- ownership may be transferable if object has `store`

## references/sui/zero-state-return.md

---
name: "zero-state-return"
description: "Trigger Vault/first-depositor pattern detected - Inject Into Depth-edge-case agent (extends existing ZERO_STATE_ECONOMICS)"
---

# ZERO_STATE_RETURN Skill (Sui)

> **Trigger**: Vault/first-depositor pattern detected
> **Inject Into**: Depth-edge-case agent (extends existing ZERO_STATE_ECONOMICS)
> **Purpose**: Check protocol return-to-zero state in Sui shared objects, not just initial zero state. Covers first depositor manipulation, residual assets, and re-entry after full exit.

## Overview

ZERO_STATE_ECONOMICS checks initial zero state. This skill EXTENDS it to cover:
- Protocol returning to zero after normal operations
- Residual assets in shared objects when supply returns to zero
- Re-entry vulnerabilities after full exit
- Sui-specific: shared objects persist even when economically empty

## 1. Identify Zero-State Transitions

| State | Trigger | Shared Object Behavior | Check |
|-------|---------|----------------------|-------|
| `total_supply == 0` | All users withdrew/burned shares | Shared pool object persists | Does this recreate first-depositor conditions? |
| `balance::value(&pool.balance) == 0` | No funds deposited | Balance<T> field is zero but exists | Are there residual rewards? |
| Empty participant set | All participants removed | Shared object fields still allocated | Can protocol still function? |
| Zero liquidity | All LP withdrawn | Pool shared object persists | What happens to accumulated fees? |

**Sui-specific**: Unlike EVM contracts (which always exist at their address), Sui shared objects CANNOT be deleted -- they persist forever once created. This means a pool/vault that reaches zero state ALWAYS allows re-entry, and its state fields retain their last values.

## 2. First Depositor Analysis

Can the first depositor manipulate the share price?

### 2a. Classic First-Depositor Attack (adapted for PTBs)

```
PTB Attack Sequence:
  1. Deposit minimum amount (1 unit) -> receive 1 share
  2. Donate large amount to inflate balance (if donation vector exists -- see TOKEN_FLOW_TRACING Section 5)
  3. Next depositor's shares are calculated against inflated balance
  4. Shares round to 0 or near-0, value captured by attacker
```

**Sui-specific considerations**:
- Steps 1-2 can happen in the SAME PTB (atomic) if donation is possible
- `balance::join` to the shared pool balance may or may not be accessible
- Check: does the protocol enforce a minimum first deposit? (`assert!(amount >= MIN_FIRST_DEPOSIT)`)
- Check: does the protocol use virtual shares/offset (e.g., mint initial phantom shares)?

### 2b. Share Price Calculation

| State | Formula | With Residual | Division by Zero? |
|-------|---------|--------------|-------------------|
| total_supply = 0, balance = 0 | {show formula} | N/A | {YES/NO -- how handled?} |
| total_supply = 0, balance > 0 | {show formula} | {inflated rate?} | {YES/NO} |
| total_supply > 0, balance = 0 | {show formula} | N/A | {YES/NO} |

**Check**: What constant is returned when total_supply = 0? Is it 1:1? Is it configurable? Can it be manipulated?

## 3. Return-to-Zero Scenarios

After normal operations, can the protocol return to zero?

### 3a. Full Exit Path

- Can ALL users withdraw their full balance? Or do rounding/dust prevent complete exit?
- After all withdrawals, what is the state of the shared pool object?
- Are there any pending operations (unlocking, vesting) that prevent zero state?

### 3b. What Persists at Zero State

| Persistent State | Value After Full Exit | Impact on Next Depositor |
|-----------------|----------------------|-------------------------|
| Accumulated rewards | {amount or 0} | {inflates rate for next depositor?} |
| Protocol fees | {amount or 0} | {captured by next depositor?} |
| Dust balances | {0 or nonzero} | {affects exchange rate?} |
| Epoch/timestamp state | {last epoch value} | {stale values used?} |
| Configuration parameters | {unchanged} | {potentially stale?} |

### 3c. Pending Operations at Zero

- Are there pending withdrawal requests that persist?
- Are there unclaimed rewards allocated to zero-address or burned shares?
- What happens to in-flight operations (epoch transitions, rebalances) when supply hits zero?

## 4. Residual Asset Check

When supply returns to zero:

### 4a. Accrued Rewards
- Do rewards persist when total_supply = 0?
- If yes -> inflates exchange rate for next depositor
- Example: Protocol accrues 100 SUI rewards, last user exits, total_supply = 0, next deposit of 1 MIST receives claim to 100 SUI

### 4b. Unclaimed Fees
- Are there fee balances stored in the shared object that persist?
- Can first new depositor capture accumulated fees?
- Example: Protocol fees = 10 SUI in Balance<SUI>, users exit, new depositor's shares priced against total balance including fees

### 4c. Dust Balances
- Can dust (tiny amounts) remain in the shared object's Balance<T>?
- Does `balance::split` leave remainder when amount cannot be evenly divided?
- Example: total_supply = 0, balance::value = 1 MIST, exchange rate undefined or manipulable

### 4d. Shared Object Storage
- Do dynamic fields persist that affect calculations?
- Are there objects stored in `Table`, `Bag`, `ObjectTable`, `ObjectBag` that survive full exit?
- Can orphaned dynamic field entries affect the next epoch of deposits?

## 5. Re-Entry Vulnerability Analysis

Does re-entering zero state recreate first-depositor attack conditions?

| Scenario | Initial State | Return-to-Zero State | Same Vulnerability? |
|----------|---------------|---------------------|---------------------|
| First depositor attack | total_supply=0, balance=0 | total_supply=0, balance=X (residual) | **WORSE** if residual > 0 |
| Exchange rate manipulation | No shares exist | No shares, but balance exists | YES + amplified |
| Donation attack | Clean shared object | Dirty shared object | YES + pre-seeded |

**Key insight**: On Sui, shared objects persist indefinitely. A pool that was active, drained, and re-entered has DIFFERENT state than a freshly created pool -- even if both have total_supply = 0.

## 5b. Default/Uninitialized State Values

For each state field used in arithmetic or control flow, check its **initial value** before any user interaction:

- **Default zero**: Move initializes struct fields to their declared defaults (typically 0 for integers, `@0x0` for addresses). If a function uses `last_timestamp`, `start_time`, or `last_update` in subtraction or division BEFORE it has ever been set, the result may be unexpected (e.g., `clock::timestamp_ms(clock) - 0` = enormous elapsed time, or division by a value derived from 0).
- **First-call path**: Trace the FIRST invocation of each state-modifying function. Does it assume a prior call already initialized dependent fields?
- **Check**: For each field read in a function, is there a code path where that field still holds its default value (0, @0x0, false)? If yes, does the function behave correctly with that default?

## 6. Protocol Reset Functions

Check for admin functions that can force zero state:

| Reset Function | Requires Cap? | Clears ALL State? | Residual After Reset |
|---------------|---------------|-------------------|---------------------|
| emergency_withdraw() | {AdminCap/OwnerCap} | {YES/NO -- which fields?} | {list remaining state} |
| rescue_tokens() | {cap type} | {NO -- only moves tokens} | {accounting mismatch?} |
| pause() | {cap type} | {NO -- just sets flag} | {all state preserved} |
| migrate() | {cap type} | {NO -- copies to new object} | {old object residual?} |

For each: what state persists in the shared object after the "reset"? Can the shared object be re-entered after reset?

## 7. Finding Template

```markdown
**ID**: [ZS-N]
**Severity**: [typically HIGH if funds extractable]
**Step Execution**: check1,2,3,4,5,6 | x(reasons) | ?(uncertain)
**Rules Applied**: [R10:check, R4:check]
**Location**: module::function:LineN
**Title**: Return-to-zero state allows [attack] due to [residual state]
**Description**:
- Protocol can return to total_supply=0 via [mechanism]
- When this happens, [state variable] retains value of [amount]
- A new depositor can [exploit path]
**Impact**: [Fund extraction / exchange rate manipulation / unfair distribution]
**PoC Scenario**:
1. Users deposit and earn rewards
2. All users withdraw, total_supply = 0
3. Rewards remain in shared object: balance::value = X
4. Attacker deposits 1 MIST
5. Attacker claims X rewards
```

## 8. Integration with ZERO_STATE_ECONOMICS

This skill does NOT replace ZERO_STATE_ECONOMICS. It EXTENDS it:

| Check | ZERO_STATE_ECONOMICS | ZERO_STATE_RETURN |
|-------|---------------------|-------------------|
| Initial zero state | YES | - |
| First depositor attack | YES | - |
| Return to zero | - | YES |
| Residual assets | - | YES |
| Re-entry vulnerability | - | YES |
| Shared object persistence | - | YES (Sui-specific) |

When applying ZERO_STATE_ECONOMICS, ALSO apply ZERO_STATE_RETURN.

## Instantiation Parameters
```
{CONTRACTS}           -- Move modules to analyze
{POOL_OBJECTS}        -- Shared pool/vault objects
{SHARE_TYPE}          -- Share/LP token type
{BALANCE_FIELDS}      -- Balance<T> fields in shared objects
{RATE_FORMULA}        -- Exchange rate calculation
{RESET_FUNCTIONS}     -- Admin reset/emergency functions
```

## Output Schema
| Field | Required | Description |
|-------|----------|-------------|
| zero_transitions | yes | How protocol can reach zero state |
| first_depositor | yes | First depositor attack analysis |
| residual_assets | yes | What persists at zero state |
| reentry_analysis | yes | Re-entry vulnerability assessment |
| reset_functions | yes | Admin reset function audit |
| finding | yes | CONFIRMED / REFUTED / CONTESTED |
| evidence | yes | Code locations with line numbers |
| step_execution | yes | Status for each step |

---

## Step Execution Checklist (MANDATORY)

| Step | Required | Completed? | Notes |
|------|----------|------------|-------|
| 1. Identify Zero-State Transitions | YES | check/x/? | |
| 2. First Depositor Analysis | YES | check/x/? | PTB atomic attack |
| 3. Return-to-Zero Scenarios | YES | check/x/? | Full exit path + persistent state |
| 4. Residual Asset Check | YES | check/x/? | Rewards, fees, dust, storage |
| 5. Re-Entry Vulnerability Analysis | YES | check/x/? | Compare initial vs return-to-zero |
| 6. Protocol Reset Functions | IF admin reset exists | check/x(N/A)/? | |

### Cross-Reference Markers

**After Step 2**: Cross-reference with TOKEN_FLOW_TRACING Section 5 for donation vectors that amplify first-depositor attacks.

**After Step 4**: If residual assets found -> check if FLASH_LOAN_INTERACTION can be used to exploit them atomically.

## scripts

```

```

## scripts/banner.sh

```bash

```

## scripts/detect-platform.py

```python
#!/usr/bin/env python3
"""Detect whether a Move project targets Sui or Aptos by scanning Move.toml files."""

import argparse
import sys
from pathlib import Path


def detect_platform(project_dir: str) -> str:
    for toml_path in Path(project_dir).rglob("Move.toml"):
        content = toml_path.read_text(errors="ignore")
        if "MystenLabs/sui.git" in content:
            return "sui"
        if "aptos-labs/aptos-core.git" in content:
            return "aptos"
        if "initia-labs/move-natives.git" in content:
            return "aptos"
    return "sui"


def main() -> None:
    parser = argparse.ArgumentParser(description="Detect Move project platform (sui / aptos)")
    parser.add_argument("path", nargs="?", default=".", help="Project root directory")
    args = parser.parse_args()

    result = detect_platform(args.path)
    if not result:
        print("ERROR: Could not detect platform. No Sui or Aptos indicators found.", file=sys.stderr)
        sys.exit(1)
    print(result)


if __name__ == "__main__":
    main()
```

