How to Audit a Perp Protocol on Sui Move

Auditing perps on Sui requires a different checklist from EVM or Aptos. The object model, hot-potato request/response pattern, Programmable Transaction Blocks, and shared collateral vaults all reshape the attack surface. This post is the Sui companion to my earlier piece on Aptos perps — same protocol class, very different failure modes.

✍ 0xTheBlackPanther 📅 Apr 2026 ⏱ 13 min read 🏷 Perps, Security, Move, Sui

Why a Separate Post for Sui

I wrote the Aptos version of this post a few weeks ago. A reader asked: “aren't they the same? It's all Move.” No. The language is shared. The runtime, the object model, the storage layout, the transaction model, and the composability surface are all different — and every one of those differences creates or kills a bug class.

This post comes out of a recent private Sui perp audit. I am deliberately keeping the details generic. The goal is to share a practical review checklist without exposing protocol-specific behavior or concrete transaction sequences.

Prereq: If you haven't read the Aptos perp audit post, start there. The seven pillars (orderbook, oracle, mark price, funding, margin, liquidation, risk engine) apply to every perp DEX — Sui included. This post assumes you know them and zooms in only on what Sui changes.

Part 1 — The Sui Perp Shape

Before reviewing a Sui perp, map its skeleton. Many designs have some version of this:

The typical Sui perp layout: GlobalConfig (shared singleton) — roles, fee addresses, versioning, protocol-wide settings
Market<BASE, LP> (shared, one per pair) — positions, order state, funding state, risk config
LpPool<LP> (shared) — counterparty LP pool, multi-collateral accounting, redeem queue
AccountRegistry (shared singleton) — owner → sub-accounts, delegate permission bitmask, escrowed coin storage
TradingRequest<C_TOKEN> (hot potato, no drop) — created by one function, consumed by another
PriceAggregator + oracle rules — multi-source price validation

The main point: settlement-critical state is often shared. That changes the audit from “who owns this resource?” to “which public paths can mutate this shared object, and under what checks?”


Part 2 — Sui-Specific Bug Hotspots

1. The Hot-Potato Request / Execute / Response Flow

Every trading operation on a well-designed Sui perp follows a four-step flow, enforced by the type system:

The hot-potato trading flow: 1. request = open_position_request(..., sender_request, ...) — builds a TradingRequest with no drop ability
2. External rules call add_witness<W>(&mut request) — proving preconditions
3. (coin, response) = execute(market, pool, request, prices, clock) — validates every witness in request_checklist is present, then dispatches
4. destroy_response(market, response) — validates response_checklist witnesses and unlocks the position reentrancy lock

Because neither TradingRequest nor TradingResponse has drop, the compiler enforces the entire flow. Forget to destroy either one, and the PTB won't type-check. This is an elegant defense — but it's not complete.

Audit target — Witness scope: A witness should prove a specific fact for a specific request, market, asset, and price context. If the witness only proves that some rule ran, the checklist may be weaker than it looks.

Audit target — Request dispatch: If a request uses an action tag, confirm each tag maps to exactly one intended handler and that request data cannot be reshaped between creation and execution.

2. Central Collateral Vaults and Dynamic-Field Storage

Many Sui perps store balances in shared or centrally-referenced vaults instead of putting a separate balance inside every position. That can be clean, but the accounting invariant becomes more important:

  1. Every token movement must update the right internal ledger.
  2. Every withdrawal must be tied to the party entitled to receive it.
  3. Every type parameter must match the stored asset identity.

The security question is not only “does the vault have enough tokens?” It is “does the vault balance still match the protocol's internal ownership ledger?”

Sui-specific: Dynamic fields are resolved at runtime, so important state may not be visible in the parent struct. Review the dynamic-field keys as part of the storage layout.

Audit target — Ledger consistency: Trace each path that moves tokens in or out of a shared vault. For each path, identify the matching ledger update and the invariant it preserves.

Audit target — Type discipline: Generic asset functions should compare caller-chosen type parameters against the asset identity already stored in protocol state before touching balances.

3. LP Share Pricing Must Use Real LP Equity

A perp LP token is not a simple receipt token over idle assets. The pool is the counterparty to open positions, so share pricing must account for more than deposited assets.

// High-level LP equity model
LP equity = pool assets
  +/- open-position value
  - obligations already owed elsewhere

If mint and redeem use a cached pool value that ignores open-position value, LP shares can be mispriced. Review this as part of the risk engine, not as a standalone LP feature.

Audit target — Stored TVL vs true AUM: Compare the value used for LP mint/redeem against the value used by trading settlement. If the two models disagree, LP share price can drift from real pool equity.

Audit target — Zero-supply bootstrap: Define what happens when LP supply reaches zero. The protocol should not leave ambiguous residual value for the next bootstrap state.

4. The Price Aggregator and Witness Quorum

Many Sui perps source prices through a weighted aggregator. This is better than a single source in theory. In practice, the edge cases cluster around quorum, freshness, and rule-scoping.

Questions to ask at the aggregator boundary: 1. What minimum quorum is required before a price is accepted?
2. Are stale or missing sources rejected cleanly?
3. If one rule is down, does the aggregator downgrade silently or abort?
4. What happens when sources disagree beyond the intended tolerance?

Audit target — Oracle freshness: Do not treat a nonzero price as fresh. Check timestamps, confidence rules, deviation limits, and whether the freshness window matches the market's risk profile.

5. The Funding Cumulative Index — Double Precision and Sign

Funding on a Sui perp is often tracked as a Double (1e18 precision) cumulative index plus a separate bool sign. Per-interval accrual often looks like:

// funding accrual (pseudocode)
elapsed_intervals = (now - last_funding_timestamp) / interval_ms
rate = basic_funding_rate * |long_oi - short_oi| * base_price / tvl_usd
delta = rate * elapsed_intervals
cumulative_index += delta // if longs pay (long_oi > short_oi)
last_funding_timestamp += elapsed_intervals * interval_ms

Audit target — Sign flips: When funding direction changes, verify both magnitude and sign update together. Unit tests should cover direction changes, zero imbalance, and multiple skipped intervals.

Audit target — Config timing: Check whether config changes apply only going forward or also affect unreconciled historical intervals. If the answer is “admin trusted,” document that trust boundary clearly.

6. Position Reentrancy Lock vs PTB Re-Entry

Sui has no synchronous external calls and no reentrancy in the EVM sense. But PTBs and hot-potato composition create a functional equivalent: a single transaction can call multiple module functions in sequence, observing and depending on state between calls. Sui perps defend against this with a position_locker: VecSet<u64> on MarketConfig — positions are locked at request creation and unlocked at response destroy.

Audit target — Lock coverage: A lock only helps if every position-mutating path uses it consistently. Build a small table of all position write paths and mark whether each one checks, sets, or releases the lock.

Audit target — Lock exceptions: Any path that intentionally skips the lock should have a simple reason: it cannot mutate existing locked state. Verify that assumption after following internal calls.

7. KeyedBigVector and Swap-Remove Pagination

Many Sui perps use keyed vector-like collections to store positions and orders. If removal reorders entries, pagination and keeper scans need careful review.

Audit target — Stable iteration: If entries can move during removal, page-based scans must define stable semantics. Otherwise off-chain operators can miss work or repeat work during stressful market conditions.

8. Symmetry Gaps Between Close, Decrease, and Withdraw

This is the most productive perp audit pattern: compare paired paths. Every invariant enforced on one path should be enforced on the corresponding path, unless the difference is intentional and documented.

The diff-the-checks exercise: Compare open vs increase, close vs decrease, direct action vs order fill, user path vs keeper path. Differences in checks are where many real findings start.

Audit target — Settlement ordering: PnL, fees, funding, reserve release, and collateral movement should be ordered consistently across equivalent paths. If the solvency check and the settlement function use different economic models, the accounting can drift.

Audit target — Minimum-size rules: Minimum collateral, minimum output, and dust rules should be applied consistently. If they differ, confirm the product reason and the operational impact.

9. Time-Basis Bugs in Borrow and Funding Checkpoints

Cumulative indexes look clean because they turn time into one number. The trap is basis mismatch: a historical index delta may be charged using current notional, reserve, or collateral price.

Audit target — Checkpoint basis: Verify what basis borrow and funding use when they are checkpointed. Historical fees should not accidentally be repriced under a later state unless that approximation is explicit and bounded.

Audit target — Accumulator design: The accumulator should make clear whether price, size, reserve, and collateral basis are sampled continuously, periodically, or only at user checkpoints.


Part 3 — The Sui-Specific Audit Playbook

Pattern 1: Map the Shared-Object Surface

Every shared object is an audit surface. List shared configs, markets, pools, account registries, and oracle objects. For each, ask: who can mutate it, through which public functions, and under which checks?

Pattern 2: Trace the Dynamic Fields

Vaults, ledgers, and auxiliary configs often live as dynamic fields. Dynamic fields are invisible in the struct definition — you only see them by grepping dof::add, dof::borrow_mut, df::exists_. Make a list of every dynamic-field key used in the protocol. For each key, ask: which paths read it, which paths write it, and is the writer the only party entitled to the value? Dynamic-field keys are the hidden state layout of the protocol.

Pattern 3: Type-Parameter Discipline

Generic functions are common in Sui perps. Remember: the caller chooses type parameters. If state already stores an asset or market identity, public functions should assert that caller-chosen types match that stored identity.

Quick check: Search for type-name reads and confirm they are used for validation, not only for events or display.

Pattern 4: Witness Checklist Completeness

Every TradingRequest has a request_checklist and a TradingResponse has a response_checklist. Both are VecSet<TypeName>. The engine validates that every listed witness is present before execution and before response destruction. That means the checklist is the access control policy.

  1. List every action code (0..8) and the checklist configured for each.
  2. For each action, ask: are the listed witnesses sufficient to prove every precondition?
  3. For each oracle or validation rule, ask: what did adding this witness actually prove? Just that the rule ran? Or that it validated this specific request?
  4. A witness that proves “the rule was called” is not the same as a witness that proves “the rule validated this request.”

Pattern 5: Account Permission Bitmask

Some account systems use permission bitmasks for delegates. Check that permission tests handle combined permissions correctly and that unused bits cannot change behavior.

Audit target — Recipient control: If a delegate can choose a withdrawal recipient, make sure the permission model explicitly allows that. Returning funds to the owner and sending funds to an arbitrary address are different powers.

Pattern 6: PTB Composition

PTBs let one transaction chain multiple calls and pass values between them. Review any request/execute flow for stale snapshots: what is read at request time, what is read at execution time, and what can change between those steps?

Pattern 7: Admin Trust Boundaries

Many Sui perps treat admin as fully trusted. That's a valid threat model, but it shifts the audit weight onto how admin changes propagate. The classic Sui perp question: when admin calls update_market_config changing basic_funding_rate_bps, does the change apply only to intervals after the timestamp of the update, or to every unreconciled interval since the last accrual? If the implementation is retroactive, document it. It's a trust-model consequence, not automatically a bug, but users should know.

Pattern 8: Known-Issue Discipline

Every private audit I've done on a mature Sui protocol started with a known-issues.md containing 15–25 items. The protocol team has already accepted these as “working as intended” or “deferred.” Re-reporting them wastes your credibility. Before writing up anything, grep the known-issues file for the affected function name and the vulnerability class. If there's an overlap, your writeup either needs an explicit “this is distinct from #N because…” paragraph, or it needs to be discarded.


Part 4 — Sui vs Aptos Move for Perp Audits

Both chains run Move. The differences that matter for perp audits:

  1. Storage model. Aptos puts resources under accounts (move_to(&signer, R)). Sui puts everything in objects that can be owned, shared, or frozen. For a perp, this means Aptos “position” is a resource under the trader's account; Sui “position” lives inside a shared Market object and is identified by a u64 key. Trust surfaces differ accordingly.
  2. Transaction model. Aptos transactions normally enter through one entry function or script. Sui PTBs let the caller compose multiple commands, pass returned objects into later commands, and interleave protocol calls in one transaction. A Sui perp audit must include PTB composition as a first-class attack surface.
  3. Capability discipline. Both languages have capabilities, but on Sui, capabilities are objects with their own ownership semantics. An AdminCap that accidentally has store can be wrapped, transferred, or put into a shared object. On Aptos, capabilities are resources held under the admin account — fewer ways for them to escape.
  4. Reentrancy. Neither Move flavor has synchronous external-call reentrancy in the EVM sense. On Sui, PTBs create a richer form of same-transaction sequencing: one command can snapshot state and a later command can consume that snapshot after other state has changed. Many Sui reentrancy-style bugs are really stale-snapshot or missing-lock bugs across PTB commands.
  5. Dynamic fields. Sui-only. The single biggest source of “where is the state actually stored?” confusion in Sui audits. On Aptos, state is exactly the struct you can see. On Sui, state is the struct plus every dynamic field attached to it at runtime.
  6. Upgrade model. Sui has UpgradeCap and published-package versioning. A Sui perp audit must include the upgrade path: which fields are added in a later version, which initial values they take, and whether every entry point is gated by a version whitelist. Aptos has its own module upgrade story but it surfaces differently in a perp audit.

Part 5 — Lessons From a Recent Sui Perp Audit

A few patterns that recurred across findings in the recent private audit:

  1. LP pricing is part of the risk engine. Mint/redeem pricing should use the same economic view of the pool as trading settlement.
  2. Equivalent paths must agree. Direct actions, order fills, keeper actions, and partial actions should enforce the same core invariants.
  3. Solvency math should preserve deficits. Rounding and saturation are useful, but they must not hide negative equity or unpaid obligations.
  4. Reserve accounting needs one basis. Book and release reserves using a consistent model, or store enough per-position data to reconcile the difference.
  5. Operational paths matter. Keeper flows, pause flags, pagination, and cleanup logic are part of protocol safety, not just off-chain UX.
  6. Tests should prove invariants, not recipes. For public writing, describe the invariant. Keep concrete transaction sequences inside private reports.
  7. Known issues save time. Read them before writing a finding, then explain what is truly new or discard the duplicate.

Quick Reference — Where Bugs Hide in Sui Perps

// Sui Perp Bug Hotspots — Ranked by Impact
ACCOUNTING
├── LP share price vs real pool equity
├── Vault balance vs internal ledger
├── Reserve booking vs reserve release
└── Solvency check vs settlement result

COMPOSABILITY
├── Request snapshot vs execution state
├── Witness scope vs requested action
├── Public functions inside PTBs
└── Position locks across all write paths

OPERATIONS
├── Keeper flow vs user flow
├── Market pause vs pool pause
├── Pagination under mutation
└── Admin config timing and documentation

Final Thoughts

Auditing perps on Sui is the same audit as auditing perps on Aptos at the mechanism layer — orderbook, oracle, funding, margin, liquidation, risk engine — and an almost entirely different audit at the implementation layer. Dynamic fields, hot-potato composability, PTB sequencing, and shared-object concurrency all reshape where the bugs live.

My working approach: map shared objects, trace request lifecycles, compare paired paths, then check whether each accounting invariant still holds. After that, read known issues and remove anything already accepted or documented.

If you're coming to Sui from EVM or Aptos perps, the biggest adjustment is accepting that state may not live where the struct makes you think it lives. Dynamic fields, object ownership, and PTB composition mean a Sui function's effective state is a superset of what you can see by reading the function. Every audit starts by making that superset explicit.

The most useful Sui perp audit habit is simple: compare the system's different views of the same value and make sure they agree.

Context: Generalized patterns from private Sui perp audit work
Companion post: How Perpetual Futures Work On-Chain — Aptos Move
Applies to: Any Sui perp with hot-potato requests, shared markets, and multi-rule oracle aggregation
Follow: @thepantherplus

Sui Move and its perp ecosystem evolve fast, and private-audit discretion means I've generalized every pattern here — if any claim is imprecise, outdated, or you spot an error, please reach out on X so I can correct it.