# Astra audit: Slawth Vawlt program

**Auditor:** Astra (OpenAI Codex CLI, the maintainer's default model) · **Date:** 2026-09-08 · **Scope:** commit `9b7b726` (program v1.2: markets, protected floor, Jupiter relay) via the packet `docs/audits/audit-packet-2026-09-08.md` · **Method:** read-only, adversarial brief, report unedited below. Fixes are tracked on the site's audits page.

---

## Summary

This audit found three High-severity deployment blockers: a permissionless keeper can manipulate the reference price used as the output floor, the globally privileged signer PDA is exposed to an incompletely constrained Jupiter CPI, and the retained upgrade authority can bypass every non-custodial guarantee. Several Medium issues weaken Token-2022 handling, route-data validation, availability, and keeper-key safety. The Anchor constraints and atomic target/transit postconditions are meaningful, but they prove only the current target balance increase and current transit depletion—not input/output provenance or preservation of other PDA-controlled balances. **Recommendation: DO NOT DEPLOY** until the High findings are resolved and the production program is tested against the exact deployed Jupiter binary and real Raydium fixtures.

## Findings

### AST-01 — High — Keeper-manipulable reference price permits execution far below fair value

**File and line:** `programs/slawth-vawlt/src/instructions/compound.rs:138-172`; `programs/slawth-vawlt/src/math.rs:25-50`

**Description:** The minimum output is derived from the reference pool's state during the keeper-controlled transaction. CLMM uses the current `sqrt_price_x64`; CPMM uses current vault reserves. Neither establishes a prior-slot, time-weighted, or independently sourced price. Jupiter's `quoted_out_amount` is keeper-authored and is not an independent bound.

**Concrete attack or failure scenario:** A keeper or bundled searcher swaps reward into target before `compound`, depressing the pool's reward/target price. It then compounds a victim at a correspondingly low output and reverses the manipulation afterward. The victim bears the bad execution while the attacker captures the sandwich profit; CPMM's size-aware formula does not help because it starts from the attacked reserves.

**Recommended code-level fix:** Use a manipulation-resistant TWAP or independent oracle with freshness, confidence, minimum-liquidity, and deviation checks. Require an observation predating the compound transaction or slot, and use current pool state only as a secondary sanity check.

**Status:** **CONFIRMED.** The missing temporal/independent price invariant is explicit in the code. A manipulate–compound–unwind PoC against the configured pool would quantify capital requirements and profit, but is not required to establish the flaw.

---

### AST-02 — High — Global signer PDA exposes unrelated custody domains to an unbound Jupiter account graph

**File and line:** `programs/slawth-vawlt/src/instructions/add_market.rs:30-40`; `programs/slawth-vawlt/src/instructions/enroll.rs:56-66`; `programs/slawth-vawlt/src/jupiter.rs:39-44`; `programs/slawth-vawlt/src/instructions/compound.rs:174-187`

**Description:** The same PDA is the unlimited delegate for every enrolled reward ATA, owns all transit accounts and fee vaults, and signs the keeper-selected Jupiter instruction. Source, destination, mint, token-program, shared-account, and adapter roles are not bound, and sensitive aliases or duplicates are not rejected. After CPI, only the current target ATA and current transit are inspected; the user reward floor, fee vault, other transits, and other delegated users are not rechecked.

**Concrete attack or failure scenario:** A keeper supplies a normal current-transit-to-current-target leg while placing a fee vault, another transit, or another user's reward ATA into an auxiliary Jupiter role. If any reachable Jupiter handler or adapter can debit that additional signer-owned or signer-delegated account, both Slawth postconditions can pass while collateral—including protected floors—is stolen. Slawth itself does not prevent this; it relies on Jupiter's internal account checks.

**Recommended code-level fix:** Separate the user-delegate, fee-vault, and Jupiter swap authorities. The PDA exposed to Jupiter should own only an isolated per-market or per-enrollment transit account and must not be delegated on user accounts. Fully deserialize each allowed Jupiter variant and bind its authority, source, final destination, source/destination mints, and token programs; reject sensitive aliases and duplicates. Reject `shared_accounts_route` in v1 unless its distinct layout is explicitly modeled.

**Status:** **PLAUSIBLE** as an exploit against the current Jupiter binary; the capability and missing Slawth invariant are **CONFIRMED**. Settle exploitability by reviewing and fuzzing the exact deployed Jupiter handlers and every reachable adapter with collateral accounts and duplicated roles.

---

### AST-03 — High — Retained upgrade authority can bypass every user protection

**File and line:** `programs/slawth-vawlt/src/instructions/initialize.rs:9-15,23-33`; `programs/slawth-vawlt/src/instructions/enroll.rs:56-66`

**Description:** Initialization correctly authenticates the loader's upgrade authority, but users subsequently approve the program PDA for `u64::MAX`. As long as the program remains upgradeable, the upgrade authority can replace the implementation with code that ignores floors, destinations, fees, pauses, and enrollment state.

**Concrete attack or failure scenario:** Compromise of the single upgrade key permits an upgrade that transfers every enrolled reward ATA's full balance, including the protected floor, and drains every authority-owned token account.

**Recommended code-level fix:** Before accepting public deposits, revoke the loader upgrade authority or place it behind a public multisig and enforced timelock with reproducible builds and an exit window. Avoid a single hot-wallet authority and consider requiring renewed user approval after security-sensitive upgrades.

**Status:** **CONFIRMED privileged trust risk**, not a permissionless exploit. The design acknowledges this trust. An on-chain `ProgramData` showing the authority revoked or transferred to the promised governance mechanism would settle remediation.

---

### AST-04 — Medium — Transfer-fee modeling is incomplete and can weaken the floor or brick markets

**File and line:** `programs/slawth-vawlt/src/instructions/compound.rs:60-70,101-172`; `programs/slawth-vawlt/src/math.rs:12-50`

**Description:** The target-token calculation uses only `transfer_fee_basis_points`, ignoring Token-2022's `maximum_fee`. It therefore understates expected net output whenever the cap binds. Separately, reloading transit captures the reward fee on user-to-transit, but the reference calculation omits the second reward fee when transit transfers into the first swap venue.

**Concrete attack or failure scenario:**

- For gross target output of 1,000,000, a 300-bps fee capped at 1,000, and 200-bps slippage, the code accepts 950,600; a cap-aware floor is approximately 979,020. A keeper can route near the weaker floor and capture the difference.
- For a reward token charging 3% per transfer and a 1% pool fee, 1,000,000 in transit yields about 960,300 at the venue, while the code requires approximately 970,200. Honest routes revert indefinitely.

**Recommended code-level fix:** Read the complete active-epoch fee configuration and calculate `min(ceil(amount × bps / 10,000), maximum_fee)` on both relevant legs. Apply the target fee to expected gross output and the reward fee to `swap_in` before reference pricing. Otherwise, explicitly reject fee-bearing reward mints and unsupported Token-2022 configurations during `add_market`.

**Status:** **CONFIRMED generic defects.** ZCAT's present monetary exposure depends on whether its active `maximum_fee` binds at expected trade sizes; decoding its mint extension would settle that market-specific impact.

---

### AST-05 — Medium — Suffix-only parsing may validate different Jupiter arguments than are executed

**File and line:** `programs/slawth-vawlt/src/jupiter.rs:17-35`; `programs/slawth-vawlt/src/instructions/compound.rs:131-136`

**Description:** Validation checks the discriminator and blindly treats the final 19 bytes as the route arguments. It does not deserialize the variable-length route plan, require canonical encoding, or require the input cursor to reach EOF.

**Concrete attack or failure scenario:** A keeper appends a fake 19-byte suffix containing the correct amount, capped slippage, and zero platform fee to a canonical route whose actual serialized arguments contain a nonzero platform fee. If Jupiter consumes the canonical arguments while tolerating trailing bytes, Jupiter executes the fee while Slawth validates the suffix. The independent target floor limits this issue's standalone loss, but the stated no-platform-fee guarantee is bypassed.

**Recommended code-level fix:** Vendor the exact Jupiter-v6 instruction types, deserialize separately by discriminator, require no trailing bytes, and optionally reserialize and byte-compare before checking the decoded fields.

**Status:** **PLAUSIBLE**, because the exact deployed Jupiter deserializer is absent from the packet. A fork test using a real route with an appended suffix, or review of the deployed handler, would settle it.

---

### AST-06 — Medium — CLMM reference math ignores trade-size price impact and tick traversal

**File and line:** `programs/slawth-vawlt/src/math.rs:25-41`; `programs/slawth-vawlt/src/instructions/compound.rs:144-152`

**Description:** CLMM expected output is calculated as input multiplied by the current spot price, less fees. It does not use active liquidity, initialized tick arrays, or price movement across ticks.

**Concrete attack or failure scenario:** Once accumulated rewards are large enough to move the reference pool by more than the 2% maximum slippage, even the best honest route cannot meet the linear-spot threshold. Because the program always compounds all balance above the floor, the enrollment remains stuck until the user manually raises the floor; a transit donation can force the same condition.

**Recommended code-level fix:** Implement an exact CLMM quote across swap steps and initialized ticks, or support bounded partial compounds and enforce a maximum input proven to remain within a price-impact budget.

**Status:** **CONFIRMED.** Cross-tick comparisons against the real Raydium implementation would establish the exact failure boundary.

---

### AST-07 — Medium — Mutable-balance quote races and transit donations permit cheap griefing

**File and line:** `programs/slawth-vawlt/src/instructions/compound.rs:79-83,91-136`; `programs/slawth-vawlt/src/jupiter.rs:30-35`; `scripts/vawlt/lib.mjs:60-69`

**Description:** The keeper quotes the entire reward balance above the floor plus the entire shared transit balance. Both balances remain externally mutable between the RPC snapshot and execution.

**Concrete attack or failure scenario:** After a keeper builds a transaction, an attacker transfers one raw reward unit to either the user reward ATA or transit. The on-chain `swap_in` then differs from the encoded route and the transaction fails atomically. Repeated dust transfers can keep quotes stale; a sufficiently large transit donation can also create an unexecutable CLMM threshold or arithmetic failure, with no isolation or sweep path.

**Recommended code-level fix:** Accept an explicit bounded `amount_to_compound <= balance - floor`. Record transit before the program transfer, validate the route against only the resulting balance delta, and require the post-CPI balance to equal the original baseline rather than zero. Prefer an isolated per-enrollment transit account.

**Status:** **CONFIRMED** for deterministic stale-quote failures; sustained ordered censorship is **PLAUSIBLE** and should be tested with adversarial transaction ordering.

---

### AST-08 — Medium — Keeper executes untrusted setup instructions with its signer privilege

**File and line:** `scripts/vawlt/lib.mjs:51-58,75`

**Description:** `toIx` accepts any program ID, instruction data, and account graph returned as a Jupiter `setupInstruction`. It rejects foreign signers but explicitly permits the keeper payer to remain a signer.

**Concrete attack or failure scenario:** A compromised or malicious Jupiter API returns a System Program transfer naming the known keeper address as signer and an attacker as recipient, followed by a route that still passes `compound`. The keeper signs and executes both, allowing its SOL balance to be stolen. Similar instructions can target keeper-owned token accounts.

**Recommended code-level fix:** Allowlist and fully decode only the expected Associated Token Program create/idempotent-create operations. Enforce payer = keeper, owner = isolated swap PDA, and mint/account derivations from the selected route. Reject every other setup program or data shape and operate the keeper from a minimally funded wallet.

**Status:** **CONFIRMED validation defect.** Exploitation requires a malicious or compromised API response rather than an on-chain caller alone.

---

### AST-09 — Low — Raydium configuration and raw vault data are not fully bound to the pool

**File and line:** `programs/slawth-vawlt/src/instructions/add_market.rs:47-72`; `programs/slawth-vawlt/src/raydium.rs:37-39,73-77`; `programs/slawth-vawlt/src/instructions/compound.rs:141-166`

**Description:** `reference_config` must have the correct program owner and a parseable fee field, but its address is never compared with the pool's embedded `amm_config`. The raw readers also do not validate account discriminators. CPMM vault addresses are matched, but their token-program owner, mint, and initialized state are not parsed before trusting bytes 64–72.

**Concrete attack or failure scenario:** An erroneous or malicious admin pairs a valid pool with an unrelated same-program config carrying a higher trade fee, lowering the output floor. A lower-fee config instead causes avoidable reverts. This is an admin/setup risk; a keeper cannot substitute the config for an existing market.

**Recommended code-level fix:** Parse and verify account discriminators and versions, extract the pool's actual configuration key and require equality, and parse CPMM vaults as token accounts with explicit owner, mint, and state checks. Recheck the stored config owner during `compound`.

**Status:** **CONFIRMED validation gap**, but permissionless exploitation is not established because market creation is admin-only.

---

### AST-10 — Low — `unenroll` can close enrollment without revoking the unlimited delegate

**File and line:** `programs/slawth-vawlt/src/instructions/unenroll.rs:18-33`; `programs/slawth-vawlt/src/instructions/enroll.rs:56-66`

**Description:** The reward ATA is optional, so a client can pass `None` even while the canonical ATA exists. Enrollment closes while the PDA's `u64::MAX` delegation remains. Conversely, if the user replaced Slawth with another delegate before unenrolling, the unconditional `revoke` removes that unrelated delegate.

**Concrete attack or failure scenario:** A faulty or malicious frontend submits `unenroll(None)`. The user sees the enrollment disappear and believes they exited, but the stale delegate remains usable by a malicious upgrade or any realized AST-02 path.

**Recommended code-level fix:** Permit the no-account path only after validating that the canonical ATA is genuinely absent. If it exists, inspect its delegate and revoke only when it equals the Slawth authority; preserve a replacement delegate.

**Status:** **CONFIRMED user/client-triggered lifecycle defect**, not independently permissionless.

## What was checked and found sound

- `initialize` derives the canonical upgradeable-loader `ProgramData` account and verifies its recorded upgrade authority.
- Config, authority, market, and enrollment PDAs use canonical seeds and stored bumps. Config, Market, and Enrollment use `init`, preventing reinitialization through their public instructions.
- Market and enrollment constraints bind the directed mint pair, token programs, user, stored reference keys, and canonical user/transit ATAs.
- Existing accounts under `init_if_needed` remain subject to ATA or token mint/authority constraints; valid third-party ATA precreation does not change ownership.
- Admin handlers require the canonical Config and `has_one = admin`; fee and slippage caps are enforced. Admin cannot withdraw fee vaults or replace an existing market's reference accounts.
- `set_floor` requires the enrolled user's signature. The direct pre-Jupiter debits sum to the amount above the floor and require the correct delegate with sufficient allowance.
- The Jupiter program address is pinned and must be executable. Keeper-supplied signer flags are stripped; only the authority key is marked as a CPI signer.
- The validated target ATA's actual spendable balance is reloaded after CPI. A wrong output mint or destination cannot commit unless the correct target ATA is independently credited by at least the threshold.
- A partial fill that leaves current transit funds or produces insufficient target output fails atomically. A fill that consumes all current transit and delivers the threshold satisfies the two intended local postconditions.
- Fee multiplication, cumulative arithmetic, and price intermediates use checked `u128`/U256 operations rather than wrapping. Failures roll back all preceding transfers and state updates.
- Raw-unit CLMM and CPMM ratios already incorporate mint decimal differences; no separate decimal normalization is required. `transfer_checked` uses the mint's actual decimals.
- CPMM math includes constant-product price impact and subtracts the listed protocol, fund, and creator fee balances.
- A normal `unenroll` with the expected live delegate revokes and closes atomically; subsequent re-enrollment creates fresh state and resets the floor to the then-current reward balance.
- No fee-vault withdrawal instruction exists in the reviewed v1 source.

## Test gaps

The supplied mock router uses an invented fixed account layout and repeats the program's last-19-byte parser, so it cannot establish compatibility or safety against the production Jupiter program. Tests that should be added include:

- Exact deployed-Jupiter or fork tests for both accepted discriminators, including a canonical instruction with an appended fake tail.
- Discriminator-specific source, destination, mint, token-program, event-authority, and shared-account layout tests.
- Duplicate/alias fuzzing and attempts to place the current reward ATA, fee vault, another market's transit, and another user's delegated ATA in every Jupiter role, with complete pre/post balance snapshots.
- Wrong-source and wrong-output routes that independently fund the validated target ATA, plus partial fills that refund or strand value outside current transit.
- Atomic Raydium manipulate–compound–unwind tests for both CLMM and CPMM reference markets.
- CLMM comparisons across tick boundaries, depleted liquidity, and trade sizes above the configured slippage budget.
- A real CPMM pool/config/vault fixture; the packet explicitly lacks the production-layout guard.
- Target transfer fees with binding and non-binding caps, rounding boundaries, 10,000 bps, epoch rollover, and split-output routes.
- Reward transfer fees across both user-to-transit and transit-to-venue transfers.
- Rejection or support tests for Token-2022 `TransferHook`, default-frozen, non-transferable, and other account-sizing or extra-account extensions.
- One-unit reward/transit changes after keeper quoting, oversized transit donations, and two markets sharing one reward mint.
- `unenroll(None)` with a live ATA, an already-revoked account, a replacement delegate, a closed ATA, and subsequent re-enrollment/counter invariants.
- Same-owner but wrong Raydium config, wrong account discriminator, and CPMM vaults with wrong owner, mint, or state.
- Malicious keeper API responses containing System Program, token-transfer, or arbitrary setup instructions.
- End-to-end v0/ALT tests against real Jupiter for transaction size, account-lock limits, compute consumption, CPI depth, and worst supported routes.
- Deployment checks confirming the intended Slawth and Jupiter upgrade-authority posture and reproducible program bytes.
