# move-auditor

Security audit of Sui Move contracts while you develop. Trigger on "audit", "check this contract", "review for security". Modes - default (full repo), DEEP (+ Sui protocol analysis), or a specific filename.

- **Kind:** skill
- **Source:** https://github.com/sanbir/move-auditor-skills
- **Page:** https://forefy.com/skills/e7852aba-fc19-44b2-a3d5-9bb0d24b893b
- **API (JSON + files):** https://forefy.com/api/skills/e7852aba-fc19-44b2-a3d5-9bb0d24b893b

---

## README.md

# Move Auditor

A security agent for **Sui Move** packages — findings in minutes, not weeks.

Attribution: architecture and audit workflow lineage from [pashov/skills](https://github.com/pashov/skills), adapted for Sui Move.

Built for:

- **Move developers** who want fast feedback before merging changes
- **Security researchers** who need a first-pass sweep over object flows and authority boundaries
- **Auditors** who want broad vector coverage before deeper manual reasoning

It is not a substitute for a full audit. It is the fast pass you should run before you trust a package.

## Demo

_Portrayed below: running the skill in a terminal workflow_

![Running move-auditor in terminal](../static/skill_pag.gif)

## Usage

```bash
# Scan the full repo (default)
/move-auditor

# DEEP mode — adds Sui protocol analysis (opus)
/move-auditor deep

# Review specific file(s)
/move-auditor sources/vault.move sources/pool.move

# Write report to a markdown file
/move-auditor --file-output
```

## Architecture (v3)

8 parallel hacking agents (sonnet), each with a specialized methodology:

| Agent | Focus |
|-------|-------|
| Vector Scan | All attack vectors from the vector bundle |
| Math Precision | Integer arithmetic, rounding, precision, decimals |
| Access Control | Capabilities, abilities, visibility, ownership |
| Economic Security | Value flows, oracles, PTB flash loans, incentives |
| Execution Trace | PTB composition, state transitions, encoding |
| Invariant | Conservation laws, state couplings, round-trips |
| Periphery | Utility modules, math libraries, base modules |
| First Principles | Assumption extraction and violation |

DEEP mode adds a 9th agent (opus) for Sui protocol-specific checklist analysis (lending, AMM, vault, staking, bridge, governance, NFT/kiosk, upgrades).

## Coverage

- **143 attack vectors** mapped to Move and Sui-specific bugs
- **8 specialized hacking agents** for parallel analysis
- **4-gate validation** (refutation, reachability, trigger, impact) with lead promotion
- **DEEP mode** for DeFi protocol checklist analysis

## What It Looks For

- leaked or forgeable capabilities and witness misuse
- shared-object access races and missing invariants across PTBs
- unsafe dynamic-field writes and upgrade paths
- coin, balance, and treasury accounting drift
- kiosk / NFT policy bypasses
- stale oracle reads and object-version assumptions
- package-init / re-init / migration mistakes
- integer precision loss and wrong rounding direction
- cross-module assumption chains

## Tips

- **Point it at the hot modules first.** `sources/` files that own shared objects, admin caps, vault balances, or upgrade state are where the highest-value bugs usually live.
- **Use `deep` for lending, AMMs, bridges, BTCfi, and upgrade-heavy packages.** Relational bugs across capabilities, objects, and PTBs need the extra reasoning pass.
- **Run more than once.** LLM output is non-deterministic — each run can surface different vulnerabilities. Two or three passes over the same code often catch things a single pass misses.

## SKILL.md

---
name: move-auditor
description: Security audit of Sui Move contracts while you develop. Trigger on "audit", "check this contract", "review for security". Modes - default (full repo), DEEP (+ Sui protocol analysis), or a specific filename.
---

# Sui Move Smart Contract Security Audit

You are the orchestrator of a parallelized Sui Move smart contract security audit.

## Mode Selection

**Exclude pattern:** skip directories `tests/`, `test/`, `build/`, `examples/`, `node_modules/` and files matching `*_test.move`, `*_tests.move`, `test_*.move`.

- **Default** (no arguments): scan all `.move` files using the exclude pattern. Use Bash `find` (not Glob).
- **DEEP**: same scope as default, but also spawns the Sui protocol analysis agent (Agent 9, opus). Use for thorough reviews of DeFi protocols. Slower and more costly.
- **`$filename ...`**: scan the specified file(s) only.

**Flags:**

- `--file-output` (off by default): also write the report to a markdown file (path per `{resolved_path}/report-formatting.md`). Never write a report file unless explicitly passed.

## Orchestration

**Turn 1 — Discover.** Print the banner, then make these parallel tool calls in one message:

a. Bash `find` for in-scope `.move` files per mode selection
b. Glob for `**/references/attack-vectors/attack-vectors-1.md` — extract the `references/` directory (two levels up) as `{resolved_path}`
c. ToolSearch `select:Agent`
d. Read the local `VERSION` file from the same directory as this skill
e. Bash `curl -sf https://raw.githubusercontent.com/sanbir/move-auditor-skills/main/move-auditor/VERSION`
f. Bash `mktemp -d /tmp/audit-XXXXXX` -> store as `{bundle_dir}`

If the remote VERSION fetch succeeds and differs from local, print `You are not using the latest version. Please upgrade for best security coverage. See https://github.com/sanbir/move-auditor-skills`. If it fails, skip silently.

**Turn 2 — Prepare.** In one message, make parallel tool calls: (a) Read `{resolved_path}/report-formatting.md`, (b) Read `{resolved_path}/judging.md`.

Then build all bundles in a single Bash command using `cat` (not shell variables or heredocs):

1. `{bundle_dir}/source.md` — ALL in-scope `.move` files, each with a `### path` header and fenced code block.
2. Agent bundles = `source.md` + agent-specific files:

| Bundle               | Appended files (relative to `{resolved_path}`)                                                                                                  |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `agent-1-bundle.md`  | `attack-vectors/attack-vectors-1.md` + `attack-vectors/attack-vectors-2.md` + `attack-vectors/attack-vectors-3.md` + `attack-vectors/attack-vectors-4.md` + `attack-vectors/attack-vectors-5.md` + `hacking-agents/vector-scan-agent.md` + `hacking-agents/shared-rules.md` |
| `agent-2-bundle.md`  | `hacking-agents/math-precision-agent.md` + `hacking-agents/shared-rules.md`                                                                     |
| `agent-3-bundle.md`  | `hacking-agents/access-control-agent.md` + `hacking-agents/shared-rules.md`                                                                     |
| `agent-4-bundle.md`  | `hacking-agents/economic-security-agent.md` + `hacking-agents/shared-rules.md`                                                                  |
| `agent-5-bundle.md`  | `hacking-agents/execution-trace-agent.md` + `hacking-agents/shared-rules.md`                                                                    |
| `agent-6-bundle.md`  | `hacking-agents/invariant-agent.md` + `hacking-agents/shared-rules.md`                                                                          |
| `agent-7-bundle.md`  | `hacking-agents/periphery-agent.md` + `hacking-agents/shared-rules.md`                                                                          |
| `agent-8-bundle.md`  | `hacking-agents/first-principles-agent.md` + `hacking-agents/shared-rules.md`                                                                   |

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

**Turn 3 — Spawn.** In one message, spawn all 8 agents as parallel foreground Agent calls. Prompt template (substitute real values):

```
Your bundle file is {bundle_dir}/agent-N-bundle.md (XXXX lines).
The bundle contains all in-scope source code and your agent instructions.
Read the bundle fully before producing findings.
```

If mode is **DEEP**, also spawn Agent 9 (Sui protocol analysis) with `model: "opus"`. Agent 9 receives the in-scope `.move` file paths and the instruction: your reference directory is `{resolved_path}`. Read `{resolved_path}/hacking-agents/sui-protocol-agent.md` for your full instructions.

**Turn 4 — Deduplicate, validate & output.** Single-pass: deduplicate all agent results, gate-evaluate, and produce the final report in one turn. Do NOT print an intermediate dedup list — go straight to the report.

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 sharing the same module and function. Keep the best version per group, number sequentially, annotate `[agents: N]`.

   Check for **composite chains**: if finding A's output feeds into B's precondition AND combined impact is strictly worse than either alone, add "Chain: [A] + [B]" at confidence = min(A, B). Most audits have 0-2.

2. **Gate evaluation.** Run each deduplicated finding through the four gates in `judging.md` (do not skip or reorder). Evaluate each finding exactly once — do not revisit after verdict.

   **Single-pass protocol:** evaluate every relevant code path ONCE in fixed order (init -> admin setters -> core operations -> deposit -> withdraw -> liquidate). One-line verdict per path: `BLOCKS`, `ALLOWS`, `IRRELEVANT`, or `UNCERTAIN`. Commit after all paths — do not re-examine. `UNCERTAIN` = `ALLOWS`.

3. **Lead promotion & rejection guardrails.**
   - Promote LEAD -> FINDING (confidence 75) if: complete exploit chain traced in source, OR `[agents: 2+]` demoted (not rejected) the same issue.
   - `[agents: 2+]` does NOT override a concrete refutation — demote to LEAD if refutation is uncertain.
   - No deployer-intent reasoning — evaluate what the code _allows_, not how the deployer _might_ use it.

4. **Fix verification** (confidence >= 80 only): trace the attack with fix applied; verify no new DoS, abort, or broken invariants; list all locations if the pattern repeats. If no safe fix exists, omit it with a note.

5. **Format and print** per `report-formatting.md`. Exclude rejected items. If `--file-output`: also write to file.

## Banner

Before doing anything else, print this exactly:

```

███╗   ███╗ ██████╗ ██╗   ██╗███████╗     █████╗ ██╗   ██╗██████╗ ██╗████████╗ ██████╗ ██████╗
████╗ ████║██╔═══██╗██║   ██║██╔════╝    ██╔══██╗██║   ██║██╔══██╗██║╚══██╔══╝██╔═══██╗██╔══██╗
██╔████╔██║██║   ██║██║   ██║█████╗      ███████║██║   ██║██║  ██║██║   ██║   ██║   ██║██████╔╝
██║╚██╔╝██║██║   ██║╚██╗ ██╔╝██╔══╝      ██╔══██║██║   ██║██║  ██║██║   ██║   ██║   ██║██╔══██╗
██║ ╚═╝ ██║╚██████╔╝ ╚████╔╝ ███████╗    ██║  ██║╚██████╔╝██████╔╝██║   ██║   ╚██████╔╝██║  ██║
╚═╝     ╚═╝ ╚═════╝   ╚═══╝  ╚══════╝    ╚═╝  ╚═╝ ╚═════╝ ╚═════╝ ╚═╝   ╚═╝    ╚═════╝ ╚═╝  ╚═╝

```

## VERSION

```

```

## assets

```

```

## assets/docs

```

```

## assets/docs/README.md

# Project Docs

**⚠️ Work in Progress — this feature is not ready yet.**

Drop any context that helps the auditor understand what the protocol is supposed to do:

- Design docs and specs
- Intended invariants
- Plain-English descriptions of protocol behavior
- Known limitations or accepted tradeoffs

Files can be plain text or markdown. To reference online docs, create a file containing one URL per line — they will be fetched and read automatically.

## assets/findings

```

```

## assets/findings/README.md

# Findings

**⚠️ Work in Progress — this feature is not ready yet.**

This directory holds two kinds of reports:

- **Reports from previous `/solidity-auditor` runs** — written automatically as `{project-name}-pashov-ai-audit-report-{timestamp}.md` each time the skill runs.
- **External audit reports** — drop any third-party or manual audit `.md` files here.

On each run the skill reads every file in this directory and re-verifies whether previously reported issues still exist in the current code. Issues still present are carried forward with a "Previously reported — still present" note. Issues that are no longer present are silently skipped.

## references

```

```

## references/attack-vectors

```

```

## references/attack-vectors/attack-vectors-1.md

# Attack Vectors Reference — Object Model, Abilities & Access Control (1/4)

> Part 1 of 5 · Vectors 1–30 of 143 total
> Covers: capability pattern, object abilities (copy/drop/store/key), visibility, access control, object leakage, type safety, transfer policies

---

**1. Missing Capability Check on Admin Function**

- **D:** Privileged function (withdraw, mint, pause, update config) callable without requiring a capability object (`AdminCap`, `TreasuryCapR`, `ManagerCap`). Any user can call the function and perform admin operations.
- **FP:** Capability parameter required (`_: &AdminCap` or `cap: &ManagerCap`). Address-based check with `assert!(ctx.sender() == admin)` present (weaker but acceptable). Function is `public(package)` visibility.

**2. Address-Based Access Control Instead of Capability**

- **D:** Access control relies on `ctx.sender() == @admin_address` instead of the capability object pattern. Hardcoded addresses break on package upgrades and are inflexible for role delegation.
- **FP:** Design explicitly requires address-based control with documented rationale. Capability pattern used instead. Address stored in a mutable config object (not hardcoded).

**3. Object Has `copy` Ability — Token Duplication**

- **D:** A value-bearing object (coin, NFT, badge, receipt) has the `copy` ability, allowing anyone to duplicate it. Attacker duplicates tokens to drain pools or mint unlimited supply.
- **FP:** Object is explicitly designed to be copyable (e.g., configuration data, read-only references). No value-bearing or authority implications. `copy` removed from struct definition.

**4. Object Has `drop` Ability — Debt/Obligation Destruction**

- **D:** An obligation object (debt record, flash loan receipt, collateral lock) has the `drop` ability. Borrower can silently destroy their debt without repaying, or destroy a collateral lock to unlock assets early.
- **FP:** Object has no financial obligation semantics. Hot potato pattern used (no `drop`, no `store`). `drop` intentionally allowed with documented rationale.

**5. Object Has `store` Ability — Unauthorized Wrapping**

- **D:** A sensitive object (capability, authority token) has `store`, allowing it to be wrapped inside another object and transferred or hidden. Attacker wraps a capability to move it outside the protocol's control.
- **FP:** `store` required for legitimate purposes (e.g., storing in dynamic fields). Transfer policies enforce correct handling. Object designed to be storable.

**6. Object Leakage via Public Function Return**

- **D:** A public function returns a capability or admin object that should remain with the protocol. Anyone calling the function captures the leaked privilege.
- **FP:** Return type is non-sensitive (data, computed value). Capability created in `init` and transferred to deployer only. Function is `public(package)`.

**7. Capability Created Outside init — Unrestricted Minting**

- **D:** Capability objects (AdminCap, TreasuryCap) created in a function other than `init`, allowing anyone to mint new capabilities. Attacker creates their own admin capability.
- **FP:** Capability creation is access-controlled (requires existing capability). Creation function is `public(package)`. One-time witness (OTW) pattern enforced.

**8. Missing One-Time Witness (OTW) Validation**

- **D:** Coin or token type created without using the one-time witness pattern. Without OTW, the `TreasuryCap` for the coin type can potentially be created by any module, enabling supply inflation.
- **FP:** `coin::create_currency` called with OTW (module name struct). `sui::types::is_one_time_witness` validated. Standard Sui coin creation pattern used.

**9. Public Entry Function Combination — Composability Break**

- **D:** Function declared as `public entry` instead of just `public` or just `entry`. The `public entry` combination prevents the function from being composed in PTBs in some contexts and creates confusing API semantics.
- **FP:** Function explicitly designed as both public and entry with documented rationale. Modern Sui version where this is properly handled.

**10. Internal Function Exposed as public Instead of public(package)**

- **D:** Function intended for internal use within the package declared as `public` instead of `public(package)`. External modules can call it, bypassing intended access restrictions.
- **FP:** Function is deliberately public for composability. All callers validated via capability checks. Function performs no sensitive operations.

**11. Missing has_one Equivalent — Object Relationship Not Validated**

- **D:** Function accepts two objects that should be related (e.g., a vault and its config, a position and its pool) but doesn't validate the relationship. Attacker passes mismatched objects.
- **FP:** Object IDs cross-referenced: `assert!(vault.pool_id == object::id(pool))`. PDA-like derivation validates relationship. Dynamic field lookup enforces parent-child relationship.

**12. Type Cosplay via Generic Type Parameter**

- **D:** Generic function accepts `T` without constraining it, allowing attacker to pass a different type than expected. E.g., `deposit<FakeToken>` instead of `deposit<USDC>` to credit the wrong balance.
- **FP:** Type constrained via phantom type on the pool/vault: `Pool<T>` ensures only matching `Coin<T>` accepted. Explicit type check against stored type identifier.

**13. Phantom Type Not Enforced on Coin Operations**

- **D:** Coin or balance operations don't leverage the phantom type parameter for safety. Different coin types can be mixed in the same pool or vault, breaking accounting.
- **FP:** `Coin<T>` and `Balance<T>` phantom types correctly partition all operations. Type parameters propagated through all function signatures.

**14. Transfer Without Policy — Capability Misdirection**

- **D:** Sensitive object transferred via `transfer::public_transfer` without a two-step or delayed transfer policy. Single-step transfer to a wrong address is irreversible, permanently losing protocol admin capability.
- **FP:** Two-step transfer wrapper used (initiate → accept). Delayed transfer with timelock. Object transferred only to verified addresses. `transfer::transfer` (non-public, owner-only) used.

**15. Missing Delayed Transfer on Admin Capability**

- **D:** Admin/treasury capability can be transferred instantly with no timelock. Compromised key or social engineering attack immediately transfers all admin power.
- **FP:** Delayed transfer wrapper with minimum delay (e.g., 24-48 hours). Multi-sig required for capability transfer. Governance vote required.

**16. Capability Stored in Shared Object — Uncontrolled Access**

- **D:** A capability object stored inside a shared object accessible to all users. Anyone can extract or use the capability through the shared object's public functions.
- **FP:** Capability stored in owned object (not shared). Access to capability within shared object gated by additional checks. Capability referenced by immutable reference only (`&Cap`).

**17. init Function Assumptions After Upgrade**

- **D:** Code assumes `init` will run again on package upgrade. In Sui, `init` only runs on first deployment — upgrades do NOT re-execute `init`. Post-upgrade initialization logic is missing.
- **FP:** Migration function exists for post-upgrade initialization. No state changes needed on upgrade. Version check pattern handles upgrade transitions.

**18. Upgrade Doesn't Update Dependencies**

- **D:** Package upgrade assumes dependent packages will also be updated. Sui package upgrades don't auto-update dependencies — old dependency versions remain in use.
- **FP:** Dependencies explicitly re-published and linked. Dependency versions checked at runtime. No breaking changes in dependencies.

**19. Missing Version Check on Shared Object**

- **D:** Shared object has no `version` field. After a package upgrade, old functions may still be called on objects, causing incompatible state transitions. No way to enforce "upgrade complete" semantics.
- **FP:** `version: u64` field present in all shared objects. Every public function checks: `assert!(obj.version == CURRENT_VERSION)`. Migration function increments version.

**20. Struct Field Reordering in Upgrade — Memory Layout Break**

- **D:** Package upgrade changes struct field order or removes fields. Objects created by the old version become incompatible, causing deserialization failures or data corruption.
- **FP:** Fields only appended (never reordered or removed). Optional fields used for forward compatibility. Version-based deserialization handles layout changes.

**21. Publisher Object Not Secured**

- **D:** `Publisher` object (created via OTW in `init`) not properly secured. Holder of Publisher can create `Display` objects and manage type metadata, potentially impersonating the protocol.
- **FP:** Publisher transferred to admin/governance on creation. Publisher stored in access-controlled object. Publisher capabilities limited by design.

**22. Kiosk/TransferPolicy Bypass**

- **D:** NFT transfer policy not enforced — NFTs extracted from Kiosk without completing required transfer policy rules (royalties, allowlist checks). Attacker bypasses royalty payments or transfer restrictions.
- **FP:** `transfer_policy::confirm_request` called with all required rules. Kiosk locked with `kiosk_lock` rule. Custom rules enforced in transfer policy.

**23. Display Object Manipulation**

- **D:** `Display` object for a type modifiable by unauthorized party, allowing spoofed metadata (fake names, images, descriptions) for tokens or NFTs.
- **FP:** Display creation requires Publisher. Display object owned by protocol admin. Display updates access-controlled.

**24. Unauthorized Object Freeze**

- **D:** Object frozen via `transfer::freeze_object` by an unauthorized user. Once frozen, the object becomes permanently immutable — legitimate owner can never modify it again.
- **FP:** Freeze operations gated by capability check. Objects frozen only during init or by admin. Object design doesn't support freezing.

**25. Unauthorized Object Sharing**

- **D:** Owned object converted to shared via `transfer::share_object` by unauthorized caller. Once shared, an owned object can never be made owned again — all users gain access.
- **FP:** Share operations gated by capability. Objects shared only during init. Shared status is intentional by design.

**26. Missing Event Emission for Critical State Changes**

- **D:** Critical operations (admin transfers, config updates, large withdrawals) don't emit events. Off-chain monitoring and indexers miss these changes, preventing timely response to attacks.
- **FP:** Events emitted for all state-changing operations. Event structs include all relevant fields. Events named in past tense (Transferred, Updated, Minted).

**27. Error Constants Not Unique**

- **D:** Same error code used for different failure conditions. When an error occurs, it's impossible to distinguish the root cause, making debugging and incident response difficult.
- **FP:** Each error constant has a unique numeric value. Error names follow `EPascalCase` convention. Error messages descriptive.

**28. Dynamic Field Not Cleaned Up Before Object Deletion**

- **D:** Parent object deleted without removing its dynamic fields. Orphaned dynamic fields become permanently inaccessible — their data and any stored value (including coins) are lost forever.
- **FP:** All dynamic fields removed before parent deletion. `dynamic_field::exists_` checked before removal. Cleanup function provided.

**29. Dynamic Object Field Exposes Wrapped Object**

- **D:** Sensitive object stored as a dynamic object field (not dynamic field). Dynamic object fields preserve the child's object ID, making it discoverable by indexers. Attacker can find and potentially interact with the "hidden" object.
- **FP:** `dynamic_field::add` used instead of `dynamic_object_field::add` for sensitive data. Object visibility is intentional. No sensitive data exposed.

**30. Missing Sui Object ID Validation**

- **D:** Function accepts an object by ID without validating it belongs to the expected type or protocol. Attacker passes an object from a different protocol with a compatible interface.
- **FP:** Object type enforced by function signature (`Account<'info, T>` equivalent). Object ownership validated. Dynamic field lookup validates parent.

## references/attack-vectors/attack-vectors-2.md

# Attack Vectors Reference — Shared Objects, PTBs & Concurrency (2/4)

> Part 2 of 5 · Vectors 31–60 of 143 total
> Covers: shared object races, programmable transaction blocks, flash loans, hot potato pattern, MEV, DoS, upgrade security, clock/time

---

**31. Shared Object Race Condition — Missing Version/Sequence Check**

- **D:** Shared object mutated by concurrent transactions without a version or sequence number check. Parallel transactions can interleave, causing inconsistent state (e.g., double-counting, lost updates in a liquidity pool).
- **FP:** `version` field checked and incremented atomically on every mutation. Mysticeti consensus ordering prevents races for this specific pattern. Single-writer pattern enforced.

**32. Shared Object DoS via Transaction Spam**

- **D:** Attacker spams transactions targeting a shared object, filling the consensus ordering queue. Legitimate transactions (liquidations, time-sensitive operations) are delayed or fail due to contention.
- **FP:** Rate limiting on shared object access. Alternative paths that don't require the contested shared object. Operations designed to be idempotent and retryable.

**33. Shared Object Used Where Owned Would Suffice**

- **D:** Object shared unnecessarily when owned-object + transfer would work. Shared objects require consensus ordering (slower, more expensive), and expose the object to contention and DoS vectors.
- **FP:** Shared object genuinely required (multi-user access: pools, DEXs, marketplaces). Documentation justifies shared status. Performance implications acceptable.

**34. PTB Flash Loan — Missing Hot Potato Pattern**

- **D:** Lending protocol allows borrow and repay as separate transactions (or within a PTB without enforcement). Borrower takes a loan, manipulates state within the PTB, and repays — or never repays at all.
- **FP:** Hot potato pattern: borrow returns a `FlashLoanReceipt` struct with no `drop` or `store` abilities, which must be consumed by the repay function within the same transaction. Compiler enforces this.

**35. Hot Potato Has drop or store Ability**

- **D:** Flash loan receipt or obligation struct incorrectly given `drop` or `store` ability. With `drop`, the borrower discards the receipt without repaying. With `store`, the receipt can be stored and repaid later (or never).
- **FP:** Receipt struct has no abilities (no `copy`, `drop`, `store`, or `key`). Only `key` if needed for object creation (then no `drop`/`store`). Compiler enforces consumption in same PTB.

**36. PTB Price Manipulation — Atomic Multi-Step Attack**

- **D:** Sui PTBs allow up to 1024 operations in a single atomic transaction. Attacker can: (1) borrow from pool A, (2) manipulate oracle/pool price, (3) execute vulnerable operation at manipulated price, (4) repay — all atomically.
- **FP:** Oracle uses TWAP or external feed (not manipulable in single tx). Price change limits per transaction. Multi-block time requirements between dependent operations.

**37. MEV via Shared Object Transaction Ordering**

- **D:** Validators can order transactions on shared objects. Attacker or colluding validator front-runs profitable transactions (sandwich attacks on DEX swaps, liquidation sniping).
- **FP:** Slippage protection with user-specified `min_amount_out`. Deadline parameter enforced. Batch processing that prevents front-running. Off-chain commit-reveal scheme.

**38. Missing Pause/Emergency Stop Mechanism**

- **D:** Protocol has no pause capability. When a vulnerability is discovered, there's no way to halt operations while a fix is deployed. Attacker drains the protocol during the response window.
- **FP:** Pause flag in shared config object. All public functions check: `assert!(!config.paused)`. Admin capability required to pause/unpause. Emergency function for immediate pause.

**39. Pause Flag Not Checked on All Functions**

- **D:** Protocol has a pause mechanism but not all critical functions check it. Attacker uses an unpaused function to drain funds during a "paused" state.
- **FP:** Every public entry function checks pause flag. Test coverage verifies all functions respect pause. Pause flag checked in shared helper function called by all handlers.

**40. Clock Object Not Used for Time-Sensitive Operations**

- **D:** Time-dependent logic (vesting, lockups, deadlines, oracle staleness) uses a hardcoded value or doesn't use `Clock` at all. Without `Clock`, there's no reliable on-chain time reference.
- **FP:** `clock::timestamp_ms(&clock)` used for all time-dependent logic. Clock passed as `&Clock` parameter (shared object at `0x6`). Timestamps stored and compared correctly.

**41. Clock Timestamp Granularity Assumption**

- **D:** Code assumes millisecond-precise execution timing. Sui's `Clock` provides epoch-level granularity — multiple transactions within the same epoch share the same timestamp. Time-sensitive arbitrage or ordering assumptions may be invalid.
- **FP:** Logic tolerant of same-timestamp transactions. No critical ordering dependent on sub-epoch time differences. Sequence numbers used instead of timestamps for ordering.

**42. Missing Deadline on Time-Sensitive Operations**

- **D:** Swap, deposit, or other time-sensitive operation has no deadline parameter. Transaction sits in the network, executes at a much later time at stale prices or unfavorable conditions.
- **FP:** `deadline_ms` parameter required: `assert!(clock::timestamp_ms(&clock) <= deadline_ms)`. User controls maximum execution time.

**43. Upgrade Capability Not Secured**

- **D:** `UpgradeCap` for the package held by a single key without timelock or multi-sig. Compromised key can deploy malicious code immediately, draining all protocol funds.
- **FP:** UpgradeCap held by multi-sig or governance. Timelock on upgrades. UpgradeCap destroyed (package made immutable). Upgrade policy restricts allowed changes.

**44. Package Made Immutable Prematurely**

- **D:** `UpgradeCap` destroyed too early (package made immutable). Critical bug discovered post-deployment cannot be fixed. All funds and state are permanently locked in buggy logic.
- **FP:** Immutability is intentional and documented. Upgrade path planned before immutability. Emergency governance mechanism exists.

**45. Upgrade Policy Too Permissive**

- **D:** Package upgrade policy allows `compatible` or `additive` changes when only `dep_only` is needed. Overly permissive policy means upgrades can change public function signatures, potentially breaking integrators or introducing vulnerabilities.
- **FP:** Upgrade policy set to minimum required level. Policy documented and justified. Governance approval required for policy changes.

**46. State Migration Missing After Upgrade**

- **D:** Package upgraded with new struct fields or logic changes, but existing on-chain objects not migrated. Old objects processed by new code with uninitialized or default values for new fields.
- **FP:** Migration function updates all existing objects to new version. Default values for new fields are safe. Version check prevents old objects from being processed by new code without migration.

**47. Reinitialization via Upgrade**

- **D:** Upgrade introduces a new "init-like" function that re-creates capabilities or resets state. Attacker (or malicious upgrader) calls it to create duplicate admin capabilities or reset protocol to initial state.
- **FP:** Init-like functions require existing admin capability. One-time guards prevent re-execution. Version check blocks re-initialization.

**48. Dynamic Dispatch via Generics — Unexpected Behavior**

- **D:** Generic function `process<T: store>(item: T)` accepts any type with `store`. Attacker passes an unexpected type that satisfies the constraint but causes unintended behavior when processed.
- **FP:** Type parameter constrained to specific types via phantom type on container. Type registry validates allowed types. Function logic type-agnostic and safe for any conforming type.

**49. Shared Object Config Update Without Timelock**

- **D:** Protocol configuration (fee rates, interest rates, collateral factors, oracle addresses) in a shared object updateable instantly by admin. Malicious or compromised admin makes a value-extracting config change with no warning.
- **FP:** Config updates have timelock (changes proposed, then executed after delay). Config update events emitted for monitoring. Range validation on all config values.

**50. Rate Limit Not Implemented on Value-Extracting Operations**

- **D:** Large withdrawals, liquidations, or transfers have no per-epoch or per-transaction rate limit. Attacker drains the entire protocol in a single transaction.
- **FP:** Per-epoch withdrawal limits enforced. Per-transaction maximum amount. Circuit breaker pauses protocol on abnormal outflows.

**51. Concurrent Shared Object Mutation — Lost Update**

- **D:** Two transactions read the same shared object value, each compute a new value, both write back. The second write overwrites the first — a classic lost update. Example: two deposits each read `total = 100`, add their amounts, both write back — second deposit "erases" the first.
- **FP:** Atomic read-modify-write pattern. Version/sequence check prevents stale writes. Sui's object versioning catches conflicts at consensus level.

**52. Hot Potato Used Across Module Boundary**

- **D:** Hot potato (receipt) struct created in one module but consumed in another, and the consuming module doesn't properly validate the receipt's contents. Attacker creates a fake receipt from a malicious module.
- **FP:** Receipt struct defined in the same module as creation and consumption. Module-level access control on receipt consumption. Receipt contents validated (amount, pool_id, etc.).

**53. Borrow-Return Mismatch in Hot Potato**

- **D:** Flash loan receipt records the borrowed amount, but the repay function doesn't validate that the returned amount matches or exceeds the receipt amount. Borrower repays less than borrowed.
- **FP:** Repay function validates: `assert!(returned_amount >= receipt.amount + fee)`. Receipt amount immutable. Balance checked after repayment.

**54. Missing Abort on Invalid State Transition**

- **D:** State machine transition (e.g., Active → Liquidating → Closed) doesn't abort on invalid transitions. Attacker transitions from Closed back to Active, reactivating a settled position.
- **FP:** Explicit state enum with transition validation. `assert!(obj.state == EXPECTED_STATE)` on every operation. Invalid transitions abort with descriptive error.

**55. Shared Object Accessed After Ownership Transfer**

- **D:** Object transferred from shared to owned (or vice versa), but other parts of the code still reference it as if it were in its original state. Access fails at runtime.
- **FP:** Ownership transitions documented and tested. References updated after ownership change. No shared-to-owned transitions (Sui doesn't allow this after initial sharing).

**56. Missing Idempotency Guard**

- **D:** Operation that should only execute once (claim, initialize, finalize) has no guard against repeated execution. Attacker calls it multiple times to extract value repeatedly.
- **FP:** Boolean flag: `assert!(!obj.claimed)` then `obj.claimed = true`. One-time object consumed on execution. Sequence/nonce prevents replay.

**57. Epoch-Based Logic Off-by-One**

- **D:** Logic comparing epochs uses `>=` where `>` is needed (or vice versa), allowing operations one epoch too early or too late. Affects vesting, lockups, and time-gated operations.
- **FP:** Epoch comparisons tested at boundary conditions. Clear documentation of inclusive vs exclusive bounds. `>=` vs `>` choice intentional and documented.

**58. Gas Exhaustion via Unbounded Operation**

- **D:** Function iterates over an unbounded collection (vector, table entries, dynamic fields) without a limit. When the collection grows large, the transaction exceeds the gas limit, permanently DoS-ing the operation.
- **FP:** Iteration bounded by constant or parameter. Pagination pattern used. `TableVec` with batch processing. Maximum collection size enforced on insertion.

**59. Cross-Module Reentrancy via PTB**

- **D:** Module A calls Module B within a PTB, and Module B calls back into Module A with state partially updated. While Move's resource model prevents classic reentrancy, PTBs enable multi-step interactions where ordering matters.
- **FP:** State fully updated before any cross-module call. No callbacks possible from called module. Operations are commutative (order doesn't matter).

**60. Validator-Controlled Transaction Ordering Exploitation**

- **D:** Validator controls the ordering of transactions touching the same shared object within an epoch. Colluding validator front-runs profitable operations or delays liquidations.
- **FP:** Operations protected by slippage/deadline parameters. Protocol doesn't depend on fair ordering. MEV-resistant design (commit-reveal, batch auctions).

## references/attack-vectors/attack-vectors-3.md

# Attack Vectors Reference — Arithmetic, Tokens & State Management (3/4)

> Part 3 of 5 · Vectors 61–90 of 143 total
> Covers: integer safety, precision loss, coin/balance operations, vector limits, dynamic fields, state lifecycle, fee logic, dust attacks

---

**61. Bitwise Operation Overflow**

- **D:** Move checks standard arithmetic overflow, but bitwise operations (`<<`, `>>`, `&`, `|`, `^`) are NOT checked. Left-shift (`<<`) can silently overflow, producing incorrect values. The Cetus $223M hack exploited a `checked_shlw` function with an incorrect shift limit (256 vs 192).
- **FP:** Custom overflow check before shift: `assert!(shift <= safe_limit)`. No bitwise operations on financial values. Shift amounts bounded by type width.

**62. Integer Overflow in Custom Math Library**

- **D:** Custom math library (fixed-point, sqrt, pow) has overflow bugs not caught by Move's default checks. The library passes unit tests but fails on edge-case inputs.
- **FP:** Library uses `u128` or `u256` intermediates for `u64` operations. Extensive fuzz testing on boundary values. Audited and well-known library used.

**63. Division Before Multiplication — Precision Loss**

- **D:** Move has no floating-point types — all math is integer. `(amount / total_supply) * price` truncates the division early, losing precision. Attacker exploits by choosing amounts that truncate to zero.
- **FP:** Multiply first: `(amount * price) / total_supply`. Higher-precision intermediate type used (u128 for u64 math). Fixed-point decimal library used.

**64. Division by Zero**

- **D:** Divisor can be zero (e.g., `total_supply`, `pool_balance`, `total_shares`). Move aborts on division by zero, causing DoS for the transaction and potentially blocking critical operations.
- **FP:** Explicit zero check: `assert!(divisor > 0, EDivisionByZero)`. Early return or special case when divisor is zero. Minimum values enforced.

**65. Integer Underflow on Subtraction**

- **D:** `a - b` where `b > a`. Move aborts on underflow in debug mode, wraps in release. Either way, the operation is incorrect — balance goes to zero (abort) or massive (wrap).
- **FP:** Explicit check: `assert!(a >= b, EUnderflow)` before subtraction. Saturating subtraction where floor of zero is correct. Checked math library used.

**66. Unsafe Type Casting**

- **D:** Casting wider to narrower type (`(value as u64)` from `u128`) without bounds check. Silently truncates, causing incorrect amounts in transfers, fees, or state updates.
- **FP:** Bounds check before cast: `assert!(value <= (U64_MAX as u128))`. Types kept consistent throughout. No narrowing casts needed.

**67. Rounding Direction Exploitation**

- **D:** Share/token calculations always round in the user's favor. Deposits round up (more shares), withdrawals round up (more tokens returned). Repeated small operations slowly drain the pool.
- **FP:** Round DOWN on deposits (fewer shares for user). Round UP on withdrawals (fewer tokens returned to user). Consistent "round against the user" policy.

**68. First Depositor Vault Inflation Attack**

- **D:** First depositor mints shares, then donates tokens directly to the vault balance (via `coin::join` or direct transfer). Share price inflates. Next depositor's deposit truncates to zero shares. First depositor redeems for everything.
- **FP:** Virtual shares/assets offset (vault starts with non-zero virtual balance). Minimum deposit enforced. Dead shares minted on init. `assert!(shares > 0)` on deposit.

**69. Round-Trip Profit**

- **D:** Due to inconsistent rounding between deposit and withdraw, `deposit(X) → withdraw(all)` returns more than X. Repeated round-trips drain the pool.
- **FP:** Rounding consistently favors the protocol in both directions. Test: `deposit(X) → withdraw(all) <= X` verified. Minimum lock period.

**70. Coin Split/Join Accounting Error**

- **D:** `coin::split` or `coin::join` used incorrectly, creating or destroying value. E.g., splitting 100 into 60 and 50 (creating 10 from nothing), or joining without adding to the tracked total.
- **FP:** Balance invariants checked after every split/join. `coin::value` verified before and after. Internal accounting matches on-chain balance.

**71. Balance vs Coin Confusion**

- **D:** `Balance<T>` (internal, no object ID) and `Coin<T>` (object with ID) used interchangeably. Converting between them without proper accounting causes lost tracking — balance exists but no coin represents it (or vice versa).
- **FP:** Clear separation: `Balance<T>` for internal state, `Coin<T>` for user-facing. `coin::into_balance` and `coin::from_balance` used with accounting. Invariant: sum of all `Balance` == sum of all `Coin`.

**72. Vector Size Limit — DoS on Unbounded Collection**

- **D:** Move vectors limited to ~1000 entries. If a vector grows beyond this limit (user registrations, positions, whitelist), insertion aborts permanently, DoS-ing the collection.
- **FP:** `sui::table_vec::TableVec` used instead of vector for unbounded data. Vector size capped with explicit check. Pagination pattern.

**73. Vector Iteration Gas Exhaustion**

- **D:** Loop iterates over entire vector on every operation. As the vector grows, gas cost increases until it exceeds the transaction gas limit, permanently blocking the operation.
- **FP:** Constant-time operations (hash table lookup). Batch processing with limit. Maximum vector size enforced. `Table` or `LinkedTable` used instead.

**74. Dynamic Field Orphaning — Value Lock**

- **D:** Parent object transferred or destroyed without removing dynamic fields. Dynamic fields become orphaned — any `Coin<T>` or valuable objects stored in them are permanently lost.
- **FP:** Cleanup function removes all dynamic fields before parent modification. `dynamic_field::exists_` checked before removal. Dynamic fields enumerated and cleaned.

**75. Fee Bypass on Alternative Code Path**

- **D:** Protocol fee applied on normal withdrawal but not on emergency withdrawal, batch operation, or admin path. Attacker routes through the fee-free path.
- **FP:** Single fee calculation function used across all paths. All exit paths charge fees. Fee-free paths have admin-only access control.

**76. Pre-Fee / Post-Fee Amount Confusion**

- **D:** Fee calculated on pre-fee amount but capacity/limit check uses post-fee amount (or vice versa). Creates accounting discrepancy — overfilling positions or under-charging fees.
- **FP:** Consistent amount used throughout (either gross or net). Fee deducted atomically. Variable naming: `amount_before_fee`, `amount_after_fee`.

**77. Fee Deduction Not Atomic**

- **D:** Fee deducted in a separate function/step from the main operation. Within a PTB, attacker skips the fee step or reorders operations to avoid fees.
- **FP:** Fee deducted within the same function as the operation. Atomic: operation fails entirely if fee fails. Hot potato enforces fee payment.

**78. Token Decimal Mismatch**

- **D:** Operations assume a specific decimal count for tokens. When a token with different decimals is used, amounts are off by orders of magnitude.
- **FP:** Coin metadata (`CoinMetadata<T>`) read for decimals. Decimal normalization applied. Only specific tokens with known decimals supported.

**79. Missing Zero-Amount Check**

- **D:** Operation accepts `amount = 0`, allowing side effects (reward snapshots, state updates, event emissions) without economic commitment.
- **FP:** `assert!(amount > 0, EZeroAmount)` on all deposit/withdraw/transfer functions. Minimum amounts enforced.

**80. Coupled State Fields Not Reset Atomically**

- **D:** Account has logically coupled fields (e.g., `shares_pending` + `total_shares`, `rewards_owed` + `last_claim_time`). On close, one is reset but not the other, leaving exploitable inconsistency.
- **FP:** All coupled fields reset in the same function call. Struct method resets all related fields atomically. Object destroyed entirely on close.

**81. Supply Invariant Violation — Mint Without Corresponding Deposit**

- **D:** Protocol mints tokens or shares without a corresponding deposit of collateral/base tokens. Total supply increases without backing, diluting all holders.
- **FP:** Mint always requires corresponding `Coin<T>` deposit. Supply invariant enforced: `total_shares * share_price == total_assets`. Mint function validates input amount.

**82. Burn Without Corresponding Withdrawal**

- **D:** Tokens burned without releasing the corresponding collateral/base tokens. The user loses tokens but receives nothing — a direct fund loss.
- **FP:** Burn always releases corresponding `Coin<T>`. Withdrawal amount calculated before burn. Atomic: burn and release in same function.

**83. Self-Transfer Inflates Accounting**

- **D:** Transfer function allows `sender == recipient`. Self-transfer triggers accounting updates (fee accrual, reward snapshots) without actual economic activity.
- **FP:** `assert!(sender != recipient)` check. Self-transfer short-circuits to no-op. Accounting unchanged on self-transfer.

**84. Missing Position Preprocessing on Transfer**

- **D:** Shares or positions transferred between users without settling pending fees/rewards on both source and destination. Destination receives unearned fees; source loses owed fees.
- **FP:** Both source and destination settled before transfer. Automatic settlement in transfer function. Fee snapshots recorded per-position.

**85. Expired Offer/Escrow Not Closeable**

- **D:** Time-limited objects (offers, escrows, locks) have no expiry-based close mechanism. Expired objects leak rent and are permanently stuck.
- **FP:** Anyone can close expired objects after deadline. `assert!(clock::timestamp_ms(&clock) >= expiry_ms)` enables permissionless cleanup.

**86. Dust Amount Locks Object**

- **D:** Tiny token amount ("dust") remaining in an object prevents it from being closed or cleaned up. Attacker sends dust to all victims' accounts.
- **FP:** Dust threshold defined — amounts below threshold ignored or swept. Force-close mechanism for dust balances. Minimum operation amounts enforced.

**87. Counter/Statistic Drift**

- **D:** Global counters (total_deposits, total_users, total_volume) updated in separate steps from the triggering operation. If counter update is skipped, the counter drifts from reality.
- **FP:** Counters updated atomically with the triggering operation. Counters re-derivable from on-chain state. Counter used for information only (not for critical logic).

**88. BCS Serialization Size Mismatch**

- **D:** Custom BCS (Binary Canonical Serialization) serialization/deserialization has a size mismatch — serialized data doesn't match the expected struct layout. Causes silent data corruption or abort.
- **FP:** Standard BCS used (no custom serialization). Struct layout stable. BCS encoding tested with round-trip verification.

**89. Unsafe `abort` in Library Function**

- **D:** Library function aborts instead of returning an error. Caller has no way to handle the error gracefully — the entire transaction fails, potentially DoS-ing a critical code path.
- **FP:** Library functions return `Option` or custom error type. Abort only on truly unrecoverable conditions. Caller can handle expected failure cases.

**90. Missing Input Validation on Instruction Arguments**

- **D:** Function accepts user-provided arguments without range or sanity checking. Values at boundaries (0, u64::MAX, negative-by-interpretation) cause overflows, underflows, or logic errors.
- **FP:** All arguments validated at function entry. Range checks: `assert!(value >= MIN && value <= MAX)`. Documentation specifies valid ranges.

## references/attack-vectors/attack-vectors-4.md

# Attack Vectors Reference — Oracle, DeFi Protocol & Platform-Level (4/4)

> Part 4 of 5 · Vectors 91–120 of 143 total
> Covers: oracle manipulation, DeFi protocol patterns, staking/rewards, liquidation, dependency risks, ZK/TEE, agent security

---

**91. Stale Oracle Price**

- **D:** Oracle price feed used without checking the `last_update` or `publish_time` timestamp. Oracle may have stopped updating — attacker exploits stale price to borrow at outdated collateral value or liquidate at favorable prices.
- **FP:** Staleness check: `assert!(clock_ms - oracle.last_update_ms <= MAX_STALE_MS)`. MAX_STALE configurable by admin. Price rejected if stale.

**92. Oracle Confidence Interval Not Validated**

- **D:** Oracle price used without checking confidence width. Wide confidence means unreliable price — attacker uses the uncertain price to extract value.
- **FP:** Confidence check: `assert!(oracle.conf * 100 / oracle.price <= MAX_CONF_PCT)`. Threshold admin-configurable. Price rejected if confidence too wide.

**93. Fake Oracle Account — Missing Source Validation**

- **D:** Oracle object accepted without validating it was created by the trusted oracle program (Pyth, Switchboard, Supra). Attacker creates a fake oracle object with manipulated prices.
- **FP:** Oracle object ID stored in config and validated: `assert!(object::id(oracle) == config.oracle_id)`. Oracle owner module validated. Hardcoded oracle addresses.

**94. Single Oracle Source — No Redundancy**

- **D:** Protocol relies on a single oracle feed. If that feed is manipulated, delayed, or goes offline, the protocol operates on bad data or is completely DoS-ed.
- **FP:** Multi-oracle aggregation (median of 3+ sources). Deviation check between oracle sources. Fallback oracle configured.

**95. Flash Loan Price Manipulation**

- **D:** Protocol uses spot pool price or reserve ratio for valuation. Attacker uses a PTB to: (1) borrow via flash loan, (2) manipulate pool price, (3) execute at manipulated price, (4) repay — all atomically.
- **FP:** TWAP or external oracle used instead of spot. Price manipulation detection (deviation check). Multi-epoch time requirement between price-dependent operations.

**96. Retroactive Oracle Pricing**

- **D:** Current oracle price used to settle positions opened at a different price. Instead of storing reference price at open time, protocol uses live price at settlement.
- **FP:** Reference price stored in position object at open time. Settlement uses stored price. Price updates only affect new positions.

**97. On-Chain Price as Slippage Reference**

- **D:** Slippage protection uses an on-chain price (oracle, pool spot) instead of user-provided expected price. Attacker manipulates on-chain price, then the "slippage check" passes against the manipulated reference.
- **FP:** Slippage parameter from user calldata (`min_amount_out`, `max_price`). Off-chain price used as reference. TWAP for slippage reference.

**98. Vault Share Inflation — First Depositor Attack**

- **D:** Empty vault allows first depositor to mint 1 share, donate tokens to inflate share price. Second depositor's deposit truncates to 0 shares. First depositor redeems everything.
- **FP:** Virtual shares/assets offset. Minimum first deposit. Dead shares minted on init. `assert!(shares > 0)`.

**99. Staking Reward Accumulator Not Updated Before Balance Change**

- **D:** Staking contract doesn't update reward accumulator (`reward_per_token`) before stake/unstake. New staker gets credit for rewards earned before they staked.
- **FP:** Accumulator updated before any balance change. `update_rewards()` called first in stake/unstake. Checkpoint pattern implemented.

**100. Flash Stake/Unstake Reward Capture**

- **D:** No minimum staking duration. Attacker stakes immediately before reward distribution, captures the reward, and unstakes in the same epoch.
- **FP:** Minimum staking/lockup period enforced. Time-weighted rewards. Snapshot from past epoch.

**101. Reward Dilution via Direct Transfer**

- **D:** Reward rate based on coin balance in the reward pool rather than internal accounting. Attacker sends tokens directly to the pool, inflating the balance and diluting or manipulating the reward rate.
- **FP:** Internal accounting (`total_staked` state variable) used for reward calculation. Direct transfers don't affect reward math.

**102. Precision Loss Zeroing Small Stakers**

- **D:** Reward calculation for small stakers rounds to zero: `(small_stake * reward_rate) / total_stake = 0`. Small stakers earn zero while their stake dilutes others.
- **FP:** High-precision accumulator (u128/u256). Minimum stake above precision threshold. Accumulated reward tracking.

**103. Liquidation Incentive Insufficient for Small Positions**

- **D:** Percentage-based liquidation bonus on dust positions doesn't cover transaction cost. Positions become permanently unliquidatable, accumulating bad debt.
- **FP:** Minimum position size enforced. Fixed minimum liquidation bonus. Dust position auto-liquidation by protocol.

**104. Self-Liquidation Profitable**

- **D:** User liquidates their own position and profits from the liquidation bonus exceeding the penalty.
- **FP:** Self-liquidation prohibited. Bonus < penalty. Health factor check prevents liquidation of healthy positions.

**105. Interest Accrual During Pause**

- **D:** Protocol pauses operations but interest continues accruing. On unpause, users face unexpected charges or liquidation from accumulated interest.
- **FP:** Interest accrual paused alongside operations. Accumulated interest during pause capped or forgiven.

**106. Bad Debt Not Socialized**

- **D:** Liquidation leaves residual debt (collateral < debt). The bad debt is not socialized — it sits in the protocol indefinitely, creating an accounting hole.
- **FP:** Bad debt socialization mechanism: spread across insurance fund, then all depositors. Automatic bad debt write-off. Insurance fund maintained.

**107. Unaudited Dependency — Library Vulnerability**

- **D:** Protocol imports an unaudited third-party library that contains a vulnerability (overflow, backdoor, incorrect rounding). The vulnerability propagates to the caller.
- **FP:** All dependencies audited. Library version pinned and reviewed. Internal implementations for critical math. Dependency audit registry.

**108. Dependency Version Not Pinned**

- **D:** Move.toml references a git dependency without a specific revision or tag. The dependency can change (main branch updated), introducing breaking changes or vulnerabilities without the protocol's knowledge.
- **FP:** Dependency pinned to specific git revision or tag. Lock file used. Dependency updates reviewed.

**109. Backdoor in Imported Module**

- **D:** Imported module contains a public init or admin function that the importing protocol doesn't expect. Attacker calls the backdoor function to mint capabilities or extract funds.
- **FP:** All imported module functions reviewed. Only specific functions called. Module source code audited.

**110. ZK Proof Replay — Missing Nullifier**

- **D:** ZK-proof verified on-chain but no nullifier tracked. Attacker replays the same proof multiple times to execute the same action repeatedly (double-spend, double-claim).
- **FP:** Nullifier stored in `Table`: `assert!(!table::contains(&nullifiers, proof.nullifier))`. Nullifier added after successful verification.

**111. ZK Proof Intent Mismatch**

- **D:** ZK-proof validates a computation but the public inputs don't bind to the on-chain action parameters. Attacker uses a valid proof for a different action than intended.
- **FP:** Public inputs include all action parameters (amount, recipient, epoch). Hash of parameters verified against proof. Intent hash validated.

**112. TEE Attestation Not Verified**

- **D:** Trusted Execution Environment (TEE) computation result accepted without verifying the attestation report. Attacker submits fake computation results.
- **FP:** Attestation report validated on-chain. Report freshness checked. Report data hash matches input hash.

**113. Agent Delegated Capability Abuse**

- **D:** AI agent holds a delegated capability (spend cap, trade authority) with no on-chain intent verification. Agent (compromised or prompt-injected) performs unauthorized actions.
- **FP:** Every agent action requires on-chain intent proof. Spend limits enforced immutably on-chain. Capability scoped (time, amount, target limited).

**114. Agent Memory Poisoning Leading to Bad Transactions**

- **D:** Adversarial input poisoning an AI agent's context (LLM context or RAG database), causing it to execute harmful transactions (approving malicious contracts, draining funds).
- **FP:** Verified intent proof required before every transaction. Agent transactions human-approved. Context isolation between agent sessions.

**115. Multi-Agent Consensus Failure**

- **D:** Multi-agent system accepts votes without signature verification. Rogue agent injects unverified votes, triggering unauthorized protocol actions.
- **FP:** Agent signatures verified on-chain. Agent membership validated. Quorum requires verified, distinct agents.

**116. Kiosk NFT Extraction Without Transfer Policy**

- **D:** Agent or user with `KioskOwnerCap` extracts NFTs directly from Kiosk without completing the transfer policy (bypassing royalties, allowlist checks).
- **FP:** `transfer_policy` enforced on all extractions. `kiosk::has_access()` verified. Transfer rules applied.

**117. Missing Invariant Check on Critical Operation**

- **D:** Protocol invariant (e.g., `total_borrows <= total_deposits + yield`, `sum(balances) == total_supply`) not checked after a state-modifying operation. Violation goes undetected, creating exploitable accounting hole.
- **FP:** Invariant assertions at end of every state-modifying function. Invariant violation aborts the transaction. Invariants tested extensively.

**118. Programmable Transaction Block Exceeds Complexity Limit**

- **D:** Complex protocol operation requires so many PTB steps that it exceeds Sui's transaction limits (gas, input count, command count). Operation becomes permanently unexpecutable.
- **FP:** Operations designed to fit within single-PTB limits. Batch/pagination for complex operations. Gas budget tested for worst case.

**119. Missing Slippage Protection on DEX Swap**

- **D:** Swap function has no user-provided minimum output amount. MEV bots sandwich the swap, extracting value from the trader.
- **FP:** `min_amount_out` parameter required and enforced. Deadline parameter prevents stale execution. Price impact limit.

**120. Package Upgrade Authority Single Point of Failure**

- **D:** `UpgradeCap` held by a single EOA. Compromised key deploys malicious upgrade, draining all protocol funds. Highest-impact vector for upgradeable packages.
- **FP:** UpgradeCap held by multi-sig or governance. Timelock on upgrades. Package immutable (UpgradeCap destroyed). Verifiable build.

## references/attack-vectors/attack-vectors-5.md

# Attack Vectors Reference — Advanced Sui Patterns, Type Safety & Real-World Exploits (5/5)

> Part 5 of 5 · Vectors 121–143 of 143 total
> Covers: generic type confusion, entry visibility bypass, event spoofing, flash loan receipt binding, dependency version contagion, denylist epoch gap, constant definition errors, cast truncation, wrapping attacks, stale external mutations

---

## V121 — Generic Type Parameter Not Validated

**What:** Functions accepting generic `<T>` (especially `Coin<T>`) don't verify `T` matches the expected/whitelisted type, allowing attackers to supply worthless self-created tokens.

**Why it matters:** This is the **#1 critical vulnerability** across real Move audits. Attacker creates `Coin<FakeUSDC>`, passes it to a lending protocol that accepts `Coin<T>`, borrows real assets against worthless collateral.

**What to look for:**
- `public fun deposit<T>(coin: Coin<T>, ...)` without checking T is in a whitelist
- Lending/borrowing functions that accept any CoinType without validation
- Generic pool functions where T isn't matched against stored pool type
- Missing `assert!(type_info::type_of<T>() == stored_type)` or equivalent

**Real-world:** Navi Protocol — all lending functions lacked CoinType validation (Critical). Econia — `place_market_order` no type check (Critical).

---

## V122 — Entry Modifier Visibility Bypass

**What:** `public(package) entry` functions are callable by anyone directly via transaction — the `entry` modifier overrides `public(package)` visibility restriction.

**Why it matters:** Developer intends function to be package-internal only, but adding `entry` makes it directly callable by any transaction. Internal-only functions with `entry` become public attack surface.

**What to look for:**
- Functions marked `public(package) entry` that contain privileged operations
- Internal admin functions with `entry` modifier
- Package-scoped functions that should NOT be callable from outside

**Secure pattern:** Functions meant to be package-internal only should NOT have the `entry` modifier. Use `public(package)` without `entry`.

---

## V123 — Caller Address as Parameter (Spoofable Sender)

**What:** Functions accept a caller/sender address as a parameter instead of deriving it from `tx_context::sender(ctx)`.

**Why it matters:** When the sender address is passed as a parameter, any caller can spoof any address. The attacker passes the victim's address and performs operations on their behalf.

**What to look for:**
- `public fun action(sender: address, ...)` instead of deriving from `TxContext`
- Address parameters used for ownership checks or balance lookups
- Missing `assert!(sender == tx_context::sender(ctx))`

**Secure pattern:** Always derive caller identity from `tx_context::sender(ctx)`, never from function parameters.

---

## V124 — Phantom Type Generic Role Bypass

**What:** Role-based capability checks using generic type parameters instead of concrete types, allowing role confusion.

**Why it matters:** If `RoleCap<T>` is used for authorization where T represents the role, a user holding `RoleCap<UserRole>` can pass it where `RoleCap<AdminRole>` was expected if the function doesn't validate T.

**What to look for:**
- `fun admin_action<T>(cap: &RoleCap<T>, ...)` without checking T is AdminRole
- Generic capability patterns where the type parameter determines privilege level
- Missing concrete type assertions on role capabilities

---

## V125 — Event Spoofing for Off-Chain Systems

**What:** Events emitted with attacker-controlled data that off-chain indexers or bridges trust without verifying the emitting contract.

**Why it matters:** Any contract can emit events that look like events from a legitimate protocol. Off-chain systems that index events without verifying the source module can be deceived into crediting fake deposits, confirming fake trades, etc.

**What to look for:**
- Off-chain indexers processing events without verifying emitting package ID
- Bridge relayers that verify event data but not event origin
- Events carrying user-supplied data without sanitization

---

## V126 — Object Wrapping/Unwrapping Attacks

**What:** Objects wrapped inside other objects become inaccessible if there's no guaranteed unwrap path. Malicious contracts can trap objects permanently by wrapping without providing an unwrap mechanism.

**Why it matters:** Wrapped objects are effectively locked — they can't be accessed, transferred, or used until unwrapped. If the wrapping contract doesn't expose unwrapping, the object is permanently lost.

**What to look for:**
- Functions that wrap user objects into container structs
- Missing corresponding unwrap/extract functions
- Third-party contracts that accept and wrap objects without guaranteed return
- Objects wrapped by contracts the owner doesn't control

---

## V127 — Table Key Collision / Duplicate Key Abort

**What:** `table::add` called without checking if the key already exists, causing an abort on duplicate entries.

**Why it matters:** If a user interacts a second time, the `table::add` aborts, creating a DoS. Attacker can front-run with entries to block legitimate operations. Need `table::contains` check or insert-or-update pattern.

**What to look for:**
- `table::add(table, key, value)` without prior `table::contains(table, key)` check
- `dynamic_field::add` without existence check
- User-interaction patterns where the same key can be added twice

---

## V128 — Timestamp Unit Confusion (ms vs seconds)

**What:** `clock::timestamp_ms()` returns milliseconds but code compares against seconds constants (or vice versa), making time-based locks effectively instant or extremely long.

**Why it matters:** If a 24-hour lock is implemented as `lock_until = now + 86400` but `now` is in milliseconds, the lock is 86 seconds. If the constant is in milliseconds but `now` is in seconds, the lock is 1000 days.

**What to look for:**
- `clock::timestamp_ms(clock)` compared to constants without `_MS` suffix
- Time calculations mixing seconds and milliseconds
- Lock duration constants that seem oddly small or large
- Missing documentation on time unit expectations

**Real-world:** SuiPad — `one_day = 0` (High). Dexlyn — `DAY_SECONDS = 600` instead of 86400 (High).

---

## V129 — Hot Potato State Reset (Nested Flash Loan)

**What:** Flash loan `start` function can be called multiple times within the same PTB, resetting the initial snapshot each time, so `finish` validates against the wrong baseline.

**Why it matters:** Attacker calls `start` (snapshot balance=1000) → borrows 500 → calls `start` again (snapshot balance=500) → `finish` checks against 500, not 1000. Attacker keeps 500.

**What to look for:**
- Flash loan `start` that overwrites snapshot without checking for existing active loan
- Hot potato receipt creation without a "loan active" flag
- Multiple `start` calls composable in same PTB
- `finish` that trusts receipt parameters instead of verifying actual balance restoration

---

## V130 — Missing Object/UID Validation

**What:** Functions accepting Sui objects without validating the UID or verifying the object originates from the expected source.

**Why it matters:** Attacker creates their own instance of the same struct type with manipulated field values (fake price, fake balance, fake authority). Without UID validation, the function accepts the attacker's object.

**What to look for:**
- Functions accepting `&T` or `&mut T` where T is a shared/owned object without ID checks
- Missing `assert!(object::id(obj) == stored_id)` on critical objects
- Oracle objects accepted without verifying they come from the registered oracle

---

## V131 — Unconditional `balance::destroy_zero` on Non-Zero Balance

**What:** `balance::destroy_zero()` called on a balance that may contain a non-zero amount, permanently destroying the remaining funds.

**Why it matters:** `destroy_zero` aborts if the balance is not zero. But if wrapped in error-swallowing logic or if a related bug causes the balance to be non-zero, funds are lost or the function becomes uncallable.

**What to look for:**
- `balance::destroy_zero(remaining)` after operations that may leave dust
- Fee collection where remainder is destroyed instead of returned
- Conditional logic where one path leaves a non-zero balance but calls `destroy_zero`

**Real-world:** Creek Finance — unconditional `destroy_zero` on non-zero balances (High).

---

## V132 — Flash Loan Receipt Pool Binding

**What:** Flash loan receipts (hot potato structs) don't store the originating pool's ID, allowing receipts from one pool to repay loans from another.

**Why it matters:** Attacker borrows from Pool A (high-value), gets receipt, repays to Pool B (low-value), and the receipt is accepted because it doesn't bind to Pool A.

**What to look for:**
- Hot potato receipt struct without a `pool_id: ID` field
- `repay` function that doesn't assert `receipt.pool_id == object::id(pool)`
- Receipt structs that only store amount, not origin
- Flash loan patterns where start/finish aren't bound to the same pool instance

**Real-world:** Cetus — `repay_flash_loan` doesn't verify `order_id` (Critical). Dexlyn — `repay_flash_swap` missing pool binding (Critical).

---

## V133 — Denylist Enforcement Epoch Gap

**What:** For regulated coins using Sui's denylist, sending is blocked instantly at validator level, but receiving is only blocked at the next epoch (~24 hours).

**Why it matters:** In cross-chain scenarios: burn tokens on source chain, target address gets denied between burn and mint, mint arrives next epoch — but the epoch gap means the mint succeeds before the deny takes effect, creating stuck or misrouted funds.

**What to look for:**
- Protocols handling regulated coins (USDC, USDT on Sui) without accounting for epoch-delayed deny
- Cross-chain bridges without denylist-aware pause mechanisms
- Transfer logic that doesn't handle the ~24h enforcement gap

---

## V134 — Dependency Upgrade Version Contagion

**What:** When a Sui package dependency upgrades, the parent package's object version checks may fail permanently because objects created by the old dependency version carry the old version number.

**Why it matters:** If Protocol A (immutable) depends on Library B (upgradeable), and Library B upgrades, Protocol A's version checks on objects created pre-upgrade fail permanently. The immutable protocol is bricked by its dependency's upgrade.

**What to look for:**
- Immutable packages depending on upgradeable packages
- Object version checks that don't account for dependency upgrades
- Missing version migration logic for dependency updates
- Critical protocols without dependency pinning strategy

---

## V135 — Return Values in Wrong Order

**What:** Multi-return functions return values in incorrect order, silently corrupting all callers.

**Why it matters:** Move doesn't name return values. If `get_reserves()` returns `(reserve_a, reserve_b)` but the implementation swaps them, every caller computes with wrong values. Swaps, prices, and collateral calculations all break.

**What to look for:**
- Functions returning multiple values of the same type (e.g., two `u64` values)
- Callers destructuring multi-return in a different order than the function produces
- Missing documentation on return value ordering

**Real-world:** KriyaDEX — `get_reserves` returned values in wrong order (High).

---

## V136 — Self-Referential Validation (Always-True Checks)

**What:** Assertion compares a value to itself (`assert!(config.version == config.version)`), which always passes and validates nothing.

**Why it matters:** Looks like a security check but is a tautology. The actual intended check (comparing against an expected version or parameter) is missing.

**What to look for:**
- `assert!(x == x)` patterns
- Version checks that compare object's version against its own field instead of an expected value
- Copy-paste errors where both sides of a comparison reference the same source

**Real-world:** Hop Aggregator — version self-comparison (High).

---

## V137 — Constant Definition Errors

**What:** Hardcoded constants contain wrong values — missing digits in MAX_U64, wrong time constants (DAY_SECONDS = 600), precision constants off by orders of magnitude.

**Why it matters:** A `MAX_U64` missing one digit means overflow checks trigger too early or too late. A `SECONDS_PER_DAY = 600` (10 minutes instead of 86400) makes time-locked functions unlock 144x faster than intended.

**What to look for:**
- `MAX_U64` or `MAX_U128` — count the digits (u64 max = 18446744073709551615, 20 digits)
- Time constants: SECONDS_PER_DAY = 86400, SECONDS_PER_YEAR = 31536000
- Precision constants: 1e6, 1e9, 1e12, 1e18 — verify digit count
- Basis points: 10000 (not 1000 or 100000)

**Real-world:** Bluefin — MAX_U64 missing digit (Critical). Dexlyn — DAY_SECONDS = 600 (High). SuiPad — one_day = 0 (High).

---

## V138 — Cast Truncation (Narrowing Casts)

**What:** Narrowing casts from larger to smaller integer types (u128 → u64, u64 → u32, u64 → u8) silently truncate high bits without overflow protection.

**Why it matters:** Unlike arithmetic operations which abort on overflow in Move, casts silently truncate. A u128 value of `2^64 + 100` cast to u64 becomes `100`, potentially bypassing amount checks.

**What to look for:**
- `(value as u64)` where value is u128 and could exceed u64 range
- `(amount as u8)` where amount could exceed 255
- Missing `assert!(value <= MAX_U64)` before narrowing casts
- Financial calculations in u128 cast down to u64 for storage

---

## V139 — Double Scaling / Unit Mixing

**What:** Scaled values (e.g., amounts multiplied by an interest index) are mixed with raw/unscaled values in the same calculation.

**Why it matters:** If `scaled_balance = balance * interest_index / 1e18` and this is later compared to or added with a raw balance, the result is meaningless. Interest calculations become wildly incorrect.

**What to look for:**
- Variables named `scaled_*` or `index_*` used in arithmetic with non-scaled variables
- Interest index multiplication applied twice (double scaling)
- Deposits stored as raw amounts compared with withdrawals stored as scaled amounts

**Real-world:** AAVE v3 on Aptos — borrow index set to token decimals instead of RAY (High). ThalaSwapV2 — double-upscaling in `pay_flashloan` (Critical).

---

## V140 — Missing Fee Withdrawal Function

**What:** Protocol collects fees into a balance or counter but provides no function to extract them.

**Why it matters:** Fees accumulate permanently with no recovery path. Protocol revenue is locked forever. This is a permanent loss of value.

**What to look for:**
- Fee collection logic that increments a balance or counter
- No corresponding `withdraw_fees` or `claim_fees` function for admin
- Fee balances stored in objects without any extraction path

---

## V141 — Recursive / Circular Function Calls

**What:** Function A calls B which calls A, creating infinite recursion that exhausts gas or stack.

**Why it matters:** Circular calls cause out-of-gas aborts on any invocation, permanently bricking affected functions. If triggered by user actions, it's a DoS.

**What to look for:**
- Fee distribution functions that call swap functions that trigger fee distribution
- Callback patterns where A → B → A is possible
- Internal helper functions called from multiple paths creating cycles

**Real-world:** Baptswap — circular function calls in fee distribution (High).

---

## V142 — Stale State from Hidden External Mutations

**What:** Function reads a value (e.g., exchange rate), then makes a cross-module call that internally mutates that same value (e.g., triggers interest accrual), making the pre-read value stale.

**Why it matters:** The function operates on outdated data after the external call changed the underlying state. Interest calculations, price lookups, and balance checks become incorrect.

**What to look for:**
- Reading a value → calling an external function → using the pre-read value
- Interest-accruing protocols where any interaction triggers accrual
- Price feeds that update on access
- Missing re-read pattern after external calls

---

## V143 — Accumulator Update Ordering in Staking

**What:** Reward accumulator updated AFTER balance change instead of before, causing incorrect reward distribution.

**Why it matters:** If the accumulator is updated after a deposit, the new deposit immediately earns historical rewards. If updated after a withdrawal, the withdrawer's final rewards use the wrong accumulator value.

**What to look for:**
- `update_rewards()` called after `increase_stake()` or `decrease_stake()`
- Deposit/withdraw functions where accumulator isn't the first operation
- Missing "accumulate before mutate" pattern

**Real-world:** Thala Labs — improper accumulator update ordering (Critical, 2 findings).

## references/hacking-agents

```

```

## references/hacking-agents/access-control-agent.md

# Access Control Agent

You are an attacker that exploits permission models. Map the complete access control surface, then exploit every gap: unprotected functions, capability leaks, broken initialization, inconsistent guards.

Other agents cover known patterns, math, state consistency, and economics. You break the permission model.

## Attack plan

**Map the permission model.** Every capability object (`AdminCap`, `OwnerCap`, `TreasuryCap`, `UpgradeCap`), every witness pattern, every inline capability/ownership check. Who creates capabilities, who receives them, who can transfer them. This map is your weapon — every attack below references it.

**Exploit inconsistent guards.** For every shared object modified by 2+ functions, find the one with the weakest guard. If function A requires `AdminCap` but function B writes the same shared object unguarded — use B. Check `public` vs `public(package)` vs `entry` visibility. Check internal helpers reachable from differently-guarded public functions.

**Exploit ability misuse.** The ability system IS access control:
- `copy` on a token type = duplication (infinite minting)
- `drop` on a debt/obligation receipt = unpaid debts
- `store` on a capability = it can be wrapped/extracted from unexpected locations
- Missing `key` on what should be an owned object = cannot be transferred to secure storage

**Hijack initialization.** `init` only runs on first publish, not on upgrades. If post-upgrade setup depends on `init` logic, the upgrade path is broken. If capabilities created in `init` are not properly secured, find the window.

**Leak capabilities.** Find functions that return capability objects, store them in shared objects (anyone can borrow), or transfer them to attacker-controlled addresses. A capability in a shared object without borrow guards = public access.

**Exploit object ownership.** Owned objects can only be used by the owner — but shared objects can be accessed by anyone. Find where the code assumes only the "right" caller will interact with a shared object. Find where owned objects are made shared without restricting access.

**Abuse package upgrades.** After upgrade, existing objects keep their old struct layout. Find where upgrade changes function behavior but old objects bypass new checks. Exploit missing version guards.

**Exploit `public(package)` trust.** Functions visible within the package are trusted by other modules. If one module in the package is compromised or has a bug, it can call `public(package)` functions in other modules — trace these trust boundaries.

## Output fields

Add to FINDINGs:
```
guard_gap: the guard that's missing — show the parallel function that has it
proof: concrete call sequence achieving unauthorized access
```

## references/hacking-agents/economic-security-agent.md

# Economic Security Agent

You are an attacker that exploits external dependencies, value flows, and economic incentives. You have unlimited capital and can compose arbitrary PTB (Programmable Transaction Block) sequences. Every dependency failure, token misbehavior, and misaligned incentive is an extraction opportunity.

Other agents cover known patterns, logic/state, access control, and arithmetic. You exploit how external dependencies, token behaviors, and economic incentives create extractable conditions.

## Attack surfaces

**Break dependencies.** For every external dependency (oracle, Coin type, cross-module call), construct a failure that permanently blocks withdrawals, liquidations, or claims. Chain failures — one stale oracle freezing an entire liquidation pipeline.

**Exploit token misbehavior.** While standard `Coin<T>` is well-behaved, custom token modules may implement transfer hooks, rebasing, blacklisting, or pausable transfers. Find where the code uses assumed amounts instead of actual received amounts and drain the difference.

**Extract value atomically via PTBs.** Construct deposit -> manipulate -> withdraw in a single PTB. PTBs allow up to 1024 operations atomically — flash loan attacks don't need a dedicated flash loan protocol. Sandwich every price-dependent operation missing deadline protection.

**Break Coin<T> assumptions.** The code may assume all `Coin<T>` behaves like SUI. Find where custom Coin types with different decimals, supply caps, or behavior are accepted without validation.

**Exploit oracle staleness.** For Pyth, Switchboard, or custom oracles on Sui:
- Stale price feeds (no timestamp check against `Clock`)
- Confidence interval ignored (using price without checking spread)
- Single-source dependency (oracle down = protocol frozen)
- Price manipulation via low-liquidity pools used as oracle source

**Abuse shared object ordering.** Sui orders transactions on shared objects via consensus. Exploit the ordering to front-run or sandwich other users' transactions on the same shared object.

**Starve shared capacity.** When multiple accounting variables share a cap or pool balance, consume all capacity with one to permanently block the other.

**Weaponize legitimate features.** Use the protocol's own mechanisms against it: deposit to manipulate governance thresholds, trigger intentional aborts to poison state, choose which path fulfills a pending request.

**Every finding needs concrete economics.** Show who profits, how much, at what cost. No numbers = LEAD.

## Output fields

Add to FINDINGs:
```
proof: concrete numbers showing profitability or fund loss
```

## references/hacking-agents/execution-trace-agent.md

# Execution Trace Agent

You are an attacker that exploits execution flow — tracing from entry point to final state through encoding, storage, branching, external calls, and state transitions. Every place the code assumes something about execution that isn't enforced is your opportunity.

Other agents cover known patterns, arithmetic, permissions, economics, invariants, periphery, and first-principles. You exploit **execution flow** across function and transaction boundaries.

## Within a transaction (single PTB)

- **Parameter divergence.** Feed mismatched inputs: claimed amount != actual Coin value, requested type != delivered type. Find every entry point with 2+ attacker-controlled inputs and break the assumed relationship between them.
- **Value leaks.** Trace every value-moving function from entry to final transfer. Find where fees are deducted from one variable but the original amount is passed downstream. Split a Coin for fees but forward the original Coin.
- **BCS encoding/decoding mismatches.** Exploit `bcs::to_bytes`/`bcs::peel_*` field order mismatches, wrong type sizes, or missing length checks when deserializing untrusted bytes.
- **Sentinel bypass.** `@0x0`, empty vectors, `option::none()` trigger special paths. Find where the special path skips validation the normal path enforces.
- **Untrusted return values.** Exploit external module call return values used without validation. Find where a query function differs from the function used for the actual operation.
- **Stale reads after external calls.** Read a shared object field, call an external module that modifies related state, then exploit the now-stale value within the same PTB.
- **Partial state updates.** Find functions that update coupled variables but can abort mid-update. Exploit the inconsistent intermediate state visible to subsequent PTB commands.
- **PTB composition attacks.** Construct multi-step PTBs where the output of command N is fed as input to command N+1 in ways the protocol didn't anticipate. Atomic flash loans via split -> use -> join without any hot potato enforcement.

## Across transactions

- **Wrong-state execution.** Execute functions in protocol states they were never designed for (paused, migrating, pre-init, post-upgrade).
- **Operation interleaving.** Corrupt multi-step operations (request -> wait -> execute) by acting between steps on shared objects.
- **Hot potato violations.** Find hot potato structs (no abilities) that can be consumed by unintended functions, or where the consumption function doesn't properly validate the receipt.
- **Mid-operation config mutation.** Fire an admin setter while an operation is in-flight. Exploit the operation consuming stale or unexpected new config values.
- **Dynamic field orphaning.** Add dynamic fields to objects, then transfer/wrap the parent — the dynamic fields become inaccessible but their value is lost.
- **Object wrapping/unwrapping side effects.** Wrap an object to hide it from access checks, unwrap to bypass guards that check object state.
- **Event spoofing.** Emit events that off-chain indexers trust. If events are used for critical off-chain logic (bridges, oracles, monitoring), forge events with misleading data.

## Output fields

Add to FINDINGs:
```
input: which parameter(s) you control and what values you supply
assumption: the implicit assumption you violated
proof: concrete trace from entry to impact with specific values
```

## references/hacking-agents/first-principles-agent.md

# First Principles Agent

You are an attacker that exploits what others can't even name. Ignore known vulnerability patterns entirely — read the code's own logic, identify every implicit assumption, and systematically violate them.

Other agents scan for known patterns, arithmetic, access control, economics, state transitions, and data flow. You catch the bugs that have no name — where the code's reasoning is simply wrong.

## How to attack

**Do not pattern-match.** Forget "reentrancy" and "oracle manipulation." For every line, ask: "this assumes X — break X."

For every state-changing function:

1. **Extract every assumption.** Values (balance is current, price is fresh), ordering (A ran before B), identity (this object is what we think), arithmetic (fits in type, nonzero denominator), state (dynamic field exists, flag was set, no concurrent modification), abilities (this type cannot be copied/dropped), ownership (this object is owned by the expected address).

2. **Violate it.** Find who controls the inputs. Construct multi-transaction sequences or PTB compositions that reach the function with the assumption broken.

3. **Exploit the break.** Trace execution with the violated assumption. Identify corrupted storage and extract value from it.

## Focus areas

- **Stale reads.** Read a shared object field, modify state via another module call, reuse the now-stale value — exploit the inconsistency.
- **Desynchronized coupling.** Two fields in a shared object must stay in sync. Find the writer that updates one but not the other.
- **Boundary abuse.** Zero, max u64, first call, last item, empty vector, supply of 1 — find where the code degenerates.
- **Cross-function breaks.** Function A leaves a shared object in configuration X. Find where function B mishandles X.
- **Assumption chains.** Module A assumes module B validates. Module B assumes module A pre-validated. Neither checks — exploit the gap.
- **Object identity assumptions.** Code assumes a particular object ID or type parameter without verifying. Supply a different object that satisfies the type signature but breaks the logic.

Do NOT report named vulnerability classes, gas optimizations, style issues, or admin-can-rug without a concrete mechanism.

## Output fields

Add to FINDINGs:
```
assumption: the specific assumption you violated
violation: how you broke it
proof: concrete trace showing the broken assumption and the extracted value
```

## references/hacking-agents/invariant-agent.md

# Invariant Agent

You are an attacker that exploits broken invariants — conservation laws, state couplings, and equivalence relationships. Map what must stay true, find the code path that violates it, and extract value from the broken state.

Other agents trace execution, check arithmetic, verify access control, analyze economics, scan patterns, audit periphery, and question assumptions. You break invariants.

## Step 1 — Map every invariant

Extract every relationship that must hold:

- **Conservation laws.** "sum of all Coin balances = total_supply", "deposited - withdrawn = vault balance". List every function that modifies any term.
- **Ability invariants.** No obligation/receipt (hot potato) can be dropped — it MUST be consumed. No token can be copied. No capability can be duplicated. Verify the ability declarations enforce these.
- **State couplings.** When X changes, Y must change too. Find all writers of X and identify which ones forget to update Y. Shared object fields that must stay synchronized.
- **Capacity constraints.** For every `assert!(value <= limit)`, find ALL paths that increase `value`. Identify paths that skip the check.
- **Object ownership invariants.** Owned objects stay with their owner unless explicitly transferred. Shared objects maintain consistent state across concurrent access.
- **Interface guarantees.** Find where view/query functions promise values that state-changing functions fail to honor.

## Step 2 — Break each invariant

- **Break round-trips.** Make `deposit(X) -> withdraw(all)` return more than X. Test with 1, max u64, first/last deposit.
- **Exploit path divergence.** Find multiple routes to the same outcome that produce different states. Take the profitable path.
- **Break commutativity.** `A.action -> B.action` vs `B.action -> A.action` produces different state. Control ordering via PTB composition or shared object transaction ordering.
- **Abuse boundaries.** Zero balance, max capacity, first/last participant, empty state — find where invariants degenerate.
- **Bypass cap enforcement.** Enumerate ALL paths modifying a capped value — settlement, fee accrual, emergency mode, admin ops. Find the path that skips the check.
- **Exploit ability violations.** Find where a struct's abilities allow actions that break protocol invariants — `store` enabling extraction from secure containers, `drop` allowing obligation destruction.
- **Exploit emergency transitions.** Break invariants during transition into or out of emergency/paused mode. Find value stranded by incomplete cleanup.

## Step 3 — Construct the exploit

For every broken invariant: what initial state is needed, what calls break it, what call extracts value, who loses.

## Output fields

Add to FINDINGs:
```
invariant: the specific conservation law, coupling, or equivalence you broke
violation_path: minimal sequence of calls that breaks it
proof: concrete values showing invariant holding before and broken after
```

## references/hacking-agents/math-precision-agent.md

# Math Precision Agent

You are an attacker that exploits integer arithmetic: rounding errors, precision loss, decimal mismatches, and scale mixing. Every truncation, every wrong rounding direction, every unchecked cast is an extraction opportunity.

Other agents cover logic, state, and access control. You exploit the math.

## Attack surfaces

**Map the math.** Identify all fixed-point systems (decimal scales, basis points, token decimals, oracle decimals), scale conversion points, and every division in value-moving functions. Move uses u64/u128/u256 — aborts on overflow by default, but custom math libraries (`mul_div`, `fixed_point32`, `fixed_point64`) may silently truncate.

**Exploit wrong rounding.** Deposits must round shares DOWN, withdrawals round assets DOWN, debt rounds UP, fees round UP. Find every division that rounds the wrong direction and drain the difference. Compoundable wrong direction = critical.

**Zero-round to steal.** Feed minimum inputs (1, smallest unit) into every calculation. Find where fees truncate to zero, rewards vanish with large total_staked, or share calculations round away entirely. A ratio truncating to zero flips formulas — exploit it.

**Amplify truncation.** Find division-before-multiplication chains — intermediate truncation amplified by later multiplication. Trace across function boundaries where a truncated return value gets multiplied.

**Overflow in custom math.** While standard Move arithmetic aborts on overflow, custom `mul_div` or `fixed_point` libraries may use unchecked intermediate results. For every `a * b / c` pattern, construct inputs where `a * b` overflows u128/u256 before the division saves it. Use flash-loan-scale values for user-influenced operands.

**Mismatch decimals.** Exploit hardcoded decimal assumptions (e.g., `1_000_000_000` for 9-decimal SUI) applied to tokens with different decimals. Feed tokens with 6, 8, or 18 decimals into code assuming a fixed scale.

**Break downcasts.** u256 -> u128 -> u64 without bounds check in custom code. Construct realistic values that overflow the target type and cause unexpected truncation or abort.

**Inflate share prices.** As the first depositor, donate to inflate the exchange rate. Make subsequent depositors round to 0 shares and steal their deposits. Check for virtual shares, minimum deposit, or dead shares mitigation.

**Every finding needs concrete numbers.** Walk through the arithmetic with specific values. No numbers = LEAD.

## Output fields

Add to FINDINGs:
```
proof: concrete arithmetic showing the bug with actual numbers
```

## references/hacking-agents/periphery-agent.md

# Periphery Agent

You are an attacker that exploits the code nobody else is looking at — utility modules, math libraries, helpers, base modules. Core modules trust this code implicitly. One bug in a 20-line helper compromises every caller.

## Prioritization

Target the smallest modules first. Math libraries, helper functions, BCS serializers/deserializers, wrapper modules, and base modules that other modules depend on are your primary attack surface.

## Attack surfaces

For every public/public(package) function in target modules:

- **Exploit unvalidated inputs.** Find inputs accepted without validation and trace what a caller blindly trusts. If the core module assumes the helper validates — verify it actually does.
- **Corrupt return values.** Return zero when non-zero is expected, truncated values, mismatched types. Every caller trusting this return value inherits the bug.
- **Exploit hidden state side effects.** Find shared object mutations, balance changes, or dynamic field writes that callers don't account for.
- **Break edge cases.** Find partial implementations that work on the happy path. Trigger the edge case that breaks them — empty vectors, zero values, max values.
- **BCS serialization bugs.** Incorrect byte ordering, missing length prefixes, wrong type widths when serializing/deserializing untrusted data. Low-level BCS manipulation is Move's equivalent of Solidity's assembly — error-prone and trusted by callers.
- **Brick via gas complexity.** Find loops over vectors or tables in utility modules whose worst-case gas cost bricks critical protocol functions. Unbounded `vector::length()` iteration, repeated `table::borrow()` calls.
- **Dynamic field manipulation.** Utility functions that add/remove/borrow dynamic fields — find where orphaning, double-add, or type confusion is possible.
- **Race provider swaps.** Exploit wrapper modules where the underlying dependency (oracle, price feed) is swapped while operations from the old source are still pending.

## references/hacking-agents/shared-rules.md

# Shared Scan Rules

## Reading

Your bundle has two sections:

1. **Core source** (inline) — read in parallel chunks (offset + limit), compute offsets from the line count in your prompt.
2. **Peripheral file manifest** — file paths under `# Peripheral Files (read on demand)`. Read only those relevant to your specialty.

When matching function names, check `fun name`, `public fun name`, `public(package) fun name`, and `public entry fun name` (Move visibility conventions).

## Cross-module patterns

When you find a bug in one module, **weaponize that pattern across every other module in the bundle.** Search by function name AND by code pattern. Finding a Balance accounting mismatch in `module_a::withdraw` means you check every other module's `withdraw` — missing a repeat instance is an audit failure.

After scanning: escalate every finding to its worst exploitable variant (DoS may hide fund theft). Then revisit every function where you found something and attack the other branches.

## Do not report

Admin-only functions doing admin things (capability-gated operations by design). Standard Move safety (abort on arithmetic overflow). Self-harm-only bugs. "AdminCap holder can rug" without a concrete mechanism.

## Output

Return structured blocks only — no preamble, no narration. Exception: vector scan agent outputs its classification block first.

FINDINGs have concrete, unguarded, exploitable attack paths. LEADs have real code smells with partial paths — default to LEAD over dropping.

**Every FINDING must have a `proof:` field** — concrete values, traces, or state sequences from the actual code. No proof = LEAD, no exceptions.

**One vulnerability per item.** Same root cause = one item. Different fixes needed = separate items.

```
FINDING | module: Name | function: func | bug_class: kebab-tag | group_key: Module | function | bug-class
path: caller -> function -> state change -> impact
proof: concrete values/trace demonstrating the bug
description: one sentence
fix: one-sentence suggestion

LEAD | module: Name | function: func | bug_class: kebab-tag | group_key: Module | function | bug-class
code_smells: what you found
description: one sentence explaining trail and what remains unverified
```

The `group_key` enables deduplication: `ModuleName | functionName | bug_class`. Agents may add custom fields.

## references/hacking-agents/sui-protocol-agent.md

# Sui Protocol Analysis Agent Instructions

You are a DeFi protocol security specialist analyzing Sui Move contracts. Instead of scanning for known patterns, you classify the protocol type and run domain-specific checklists.

## Critical Output Rule

You communicate results back ONLY through your final text response. Do NOT write any files. Your only job is to return findings as text.

## Workflow

1. Read all in-scope `.move` files, plus `judging.md` and `report-formatting.md` from the reference directory, in a single parallel batch.
2. **Classify the protocol type.** Determine which category (or categories) the codebase falls into.
3. **Run the relevant checklist(s).** For each item, determine if implemented. If omission is exploitable, apply the FP gate.
4. Final response MUST contain every finding **already formatted per `report-formatting.md`**. Use placeholder sequential numbers.
5. If NO findings, respond with "No findings."

---

## Protocol Checklists

### Lending / Borrowing (14 items)

1. Health factor includes accrued (not just principal) interest
2. Liquidation bonus covers transaction cost for minimum-size positions
3. Self-liquidation not profitable (bonus < penalty)
4. Collateral withdrawal blocked when underwater
5. Interest accrual paused when protocol operations paused
6. Multi-decimal tokens handled correctly in liquidation math
7. Oracle price validated: staleness check + confidence interval
8. Bad debt socialization mechanism exists
9. Interest rate model doesn't overflow at extreme utilization
10. Borrow cap enforced per-asset and globally
11. PTB flash loan can't be used to manipulate → borrow → repay atomically
12. Partial liquidation doesn't leave unliquidatable dust
13. Collateral factor updates don't retroactively liquidate positions
14. Reserve factor deducted correctly from lender yield

### AMM / DEX (10 items)

1. Slippage from user calldata, not on-chain pool state
2. Deadline parameter enforced (`assert!(clock_ms <= deadline_ms)`)
3. Multi-hop: slippage on final output, not intermediates
4. LP value from tracked reserves, not raw Balance amount
5. Fee from pool config, not hardcoded
6. Invariant (constant product or other) verified after every swap
7. Flash swap callback restricted (only pool can call back via hot potato)
8. Single-sided add doesn't bypass fee accounting
9. Minimum liquidity locked on pool creation
10. Price impact check prevents extreme trades

### Vault / Token Accounting (10 items)

1. First-depositor inflation mitigated (virtual shares, minimum deposit, dead shares)
2. Rounding: deposits round DOWN, withdrawals round UP (against user)
3. Round-trip not profitable: deposit(X) → withdraw(all) ≤ X
4. Share price not manipulable via direct `coin::join` to vault balance
5. Withdraw can't take more than depositor's proportional share
6. Accounting uses internal `Balance<T>`, not external coin tracking
7. Rebase/interest-bearing tokens handled if supported
8. Emergency withdraw still enforces share accounting
9. Total supply updated on every deposit/withdraw
10. Zero-share mint prevented: `assert!(shares > 0)`

### Staking / Rewards (10 items)

1. Reward accumulator updated before any balance change
2. No flash stake/unstake capture (minimum duration or time-weighted)
3. Precision loss doesn't zero small stakers
4. Cooldown not griefable by dust deposits from others
5. Reward transfer accounts for actual amount received
6. Direct transfer to pool doesn't inflate reward rate
7. Unstake returns correct amount (considers penalties)
8. Multiple reward tokens have independent accumulators
9. Reward rate update doesn't retroactively change earned rewards
10. Position transfer settles rewards on both source and destination

### Bridge / Cross-Chain (9 items)

1. Message replay protection (nonce, hash dedup)
2. Source chain and sender validated
3. Rate limits (per-tx and per-epoch)
4. Decimal conversion handles all combinations
5. Supply invariant: minted ≤ locked
6. Finality: action only after sufficient confirmations
7. Pause mechanism with immediate effect
8. Validator diversity (not single point of failure)
9. Fee accounting doesn't create discrepancy

### Governance (6 items)

1. Vote weight from past epoch (not current — prevents flash-vote)
2. Timelock between passage and execution
3. Quorum from total supply, not circulating
4. No double-voting via token transfer
5. Execution restricted to passed + timelocked proposals
6. Emergency bypass only with sufficient threshold

### NFT / Kiosk (8 items)

1. Transfer policy enforced on all extractions from Kiosk
2. Royalties collected before transfer completes
3. KioskOwnerCap properly secured (not leaked)
4. Listing/delisting atomic with payment
5. NFT metadata (Display) only modifiable by Publisher holder
6. Kiosk lock rules prevent unauthorized extraction
7. Allowlist/denylist rules enforced in transfer policy
8. Creator royalty percentage bounded and validated

### Package Upgrade Safety (8 items)

1. UpgradeCap held by multi-sig or governance
2. Upgrade has timelock/delay
3. All shared objects have `version` field
4. Every public function checks version: `assert!(obj.version == CURRENT)`
5. Struct fields append-only (no reorder/remove)
6. Migration function handles old → new objects
7. `init` logic not depended upon for upgrades
8. Upgrade policy set to minimum required level

## references/hacking-agents/vector-scan-agent.md

# Vector Scan Agent

You are an attacker that exploits known attack vectors. Armed with your vector bundle, grind through every one, find every manifestation in this codebase, and exploit it.

## How to attack

For each vector, extract the root cause and hunt ALL manifestations — different names, token types, structures. A "stale cached Balance value" vector applies wherever code caches cross-module state.

- Construct AND concept both absent -> skip
- Guard unambiguously blocks the attack -> skip
- No guard, partial guard, or guard that might not cover all paths -> investigate and exploit

For every vector worth investigating, trace the full attack path: confirm reachability, follow cross-function interactions, find the gap that lets you through.

## Break guards

A guard only stops you if it blocks ALL paths. Find the way around:
- Reach the same state through a function without the guard
- Feed input values that slip past the assertion
- Exploit checks positioned after external module calls (too late)
- Enter through PTB composition, wrapping/unwrapping, or dynamic field access
- Bypass capability checks via shared objects storing capabilities without borrow guards

## Output gate

Your response MUST begin with the vector classification block:

```
Skip: V1,V2,V5
Drop: V4,V9
Investigate: V3,V7
Total: 7 classified
```

Every vector in exactly one category. `Total` matches vector count. After the classification block, output FINDING and LEAD blocks.

## references/judging.md

# Finding Validation

Every finding passes four sequential gates. Fail any gate -> **rejected** or **demoted** to lead. Later gates are not evaluated for failed findings.

## Gate 1 — Refutation

Construct the strongest argument that the finding is wrong. Find the guard, assertion, capability check, or ability restriction that kills the attack — quote the exact line and trace how it blocks the claimed step.

- Concrete refutation (specific guard blocks exact claimed step) -> **REJECTED** (or **DEMOTE** if code smell remains)
- Speculative refutation ("probably wouldn't happen") -> **clears**, continue

## Gate 2 — Reachability

Prove the vulnerable state exists in a live deployment. Consider shared vs owned objects — shared objects are accessible by any transaction, owned objects only by the owner.

- Structurally impossible (enforced invariant, ability restriction prevents it) -> **REJECTED**
- Requires privileged actions outside normal operation (AdminCap, UpgradeCap) -> **DEMOTE**
- Achievable through normal usage, PTB composition, or common Coin behaviors -> **clears**, continue

## Gate 3 — Trigger

Prove an unprivileged actor executes the attack.

- Only capability holders can trigger -> **DEMOTE**
- Costs exceed extraction -> **REJECTED**
- Unprivileged actor triggers profitably -> **clears**, continue

## Gate 4 — Impact

Prove material harm to an identifiable victim.

- Self-harm only -> **REJECTED**
- Dust-level, no compounding -> **DEMOTE**
- Material loss to identifiable victim -> **CONFIRMED**

## Confidence

Start at **100**, deduct: partial attack path **-20**, bounded non-compounding impact **-15**, requires specific (but achievable) state **-10**. Confidence >= 80 gets description + fix. Below 80 gets description only.

## Safe patterns (do not flag)

- Standard Move arithmetic (aborts on overflow/underflow by default)
- Capability-gated admin functions (AdminCap, OwnerCap patterns)
- Hot potato enforcement (no-ability structs consumed in same transaction)
- Object ownership isolation (owned objects inaccessible to non-owners)
- `assert!` guards that cover the claimed attack path
- Virtual shares or minimum deposit mitigating first-depositor inflation
- Consistent protocol-favoring rounding unless compounding or zero-rounding

## Lead promotion

Before finalizing leads, promote where warranted:

- **Cross-module echo.** Same root cause confirmed as FINDING in one module -> promote in every module where the identical pattern appears.
- **Multi-agent convergence.** 2+ agents flagged same area, lead was demoted (not rejected) -> promote to FINDING at confidence 75.
- **Partial-path completion.** Only weakness is incomplete trace but path is reachable and unguarded -> promote to FINDING at confidence 75, description only.

## Leads

High-signal trails for manual investigation. No confidence score, no fix — title, code smells, and what remains unverified.

## Do Not Report

Linter/compiler issues, gas micro-opts, naming, documentation. Admin/capability privileges by design. Missing events. Centralization without exploit path. Implausible preconditions (but custom Coin behavior, oracle failure, and shared object contention ARE plausible for protocols accepting arbitrary tokens or using shared objects).

## references/report-formatting.md

# Report Formatting

## Report Path

Save the report to `assets/findings/{project-name}-move-ai-audit-report-{timestamp}.md` where `{project-name}` is the repo root basename and `{timestamp}` is `YYYYMMDD-HHMMSS` at scan time.

## Output Format

````
# Security Review — <ModuleName or repo name>

---

## Scope

|                                  |                                                        |
| -------------------------------- | ------------------------------------------------------ |
| **Mode**                         | ALL / default / filename                               |
| **Framework**                    | Sui Move                                               |
| **Files reviewed**               | `file1.move` · `file2.move`<br>`file3.move`            | <!-- list every file, 3 per line -->
| **Attack vectors checked**       | N (across vector-scan agent)                           |
| **Agents deployed**              | N hacking agents [+ sui-protocol]                      |
| **Confidence threshold (1-100)** | N                                                      |

---

## Findings

[95] **1. <Title>**

`module::function_name` · Confidence: 95

**Description**
<The vulnerable code pattern and why it is exploitable, in 1 short sentence>

**Fix**

```diff
- vulnerable line(s)
+ fixed line(s)
```
---

[82] **2. <Title>**

`module::function_name` · Confidence: 82

**Description**
<The vulnerable code pattern and why it is exploitable, in 1 short sentence>

**Fix**

```diff
- vulnerable line(s)
+ fixed line(s)
```
---

< ... all above-threshold findings >

---

[75] **3. <Title>**

`module::function_name` · Confidence: 75

**Description**
<The vulnerable code pattern and why it is exploitable, in 1 short sentence>

---

< ... all below-threshold findings (description only, no Fix block) >

---

Findings List

| # | Confidence | Title |
|---|---|---|
| 1 | [95] | <title> |
| 2 | [82] | <title> |
| 3 | [75] | <title> |

---

## Leads

_Vulnerability trails with concrete code smells where the full exploit path could not be completed in one analysis pass. These are not false positives — they are high-signal leads for manual review. Not scored._

- **<Title>** — `module::function` — Code smells: <missing guard, unsafe arithmetic, etc.> — <1-2 sentence description of the trail and what remains unverified>
- **<Title>** — `module::function` — Code smells: <...> — <1-2 sentence description>

---

> This review was performed by an AI assistant. AI analysis can never verify the complete absence of vulnerabilities and no guarantee of security is given. Team security reviews, formal verification with Move Prover, bug bounty programs, and on-chain monitoring are strongly recommended.

````

**Rules:** Follow the template above exactly. Sort findings by confidence (highest first). Findings below the threshold get a description but no **Fix** block. Draft findings directly in report format — do not re-generate.

