# Astra verification: Slawth Vawlt program v1.6

**Auditor:** Astra (OpenAI Codex CLI, the maintainer's default model) · **Date:** 2026-09-08 · **Scope:** the v1.6 working tree on top of commit `bb994af` (v1.5), via a packet with the full source, harness, tests, client, keeper and live mainnet evidence · **Method:** read-only, report unedited below except that local file paths were redacted.

**Response:** AST-11/12/15/16/19 verified complete; AST-13 stays open until the first real compound is traced as specified at the end of the report; AST-14/18 residuals and the new AST-20 to AST-24 are being fixed in v1.7 before the freeze (AST-20 to AST-22 concern CPMM reference pools only, which neither launch market uses). Per-finding status is on the site's audits page.


## Summary

v1.6 closes the narrow defects in AST-11, AST-12, AST-15, AST-16, and AST-19, and materially improves AST-14 and AST-18, but AST-13 remains unsettled and AST-03, AST-05, F4, and F9 remain missing. The two configured launch markets are CLMM-based, so the newly identified generic-CPMM loss paths are dormant for the proposed proof deployment but unacceptable in an immutable generic program. **Recommendation (a): MAY DEPLOY** v1.6 only for maintainer-only proof compounds from the two maintainer wallets, using small amounts at or above the configured minima, with no third-party enrollment or approval, manual finalized-trace review, and the upgrade authority retained. **Recommendation (b): DO NOT BURN THE UPGRADE AUTHORITY** until reproducible deployed bytecode and all 65 tests are independently verified, AST-13 is settled by the trace specified below for every allowed fee-bearing route shape, AST-05 and the CPMM findings are fixed or CPMM is made unreachable, remaining PARTIAL/MISSING items are fixed or explicitly risk-accepted, keeper accounting defects are corrected, live configurations and external-program versions are pinned, and the final `ProgramData.upgrade_authority_address == None` transition is independently verified before any third-party approval.

Line numbers below are source-relative to the files embedded in the verification packet.

## Verification verdicts

| ID | Verdict | v1.6 evidence | Residual risk |
|---|---|---|---|
| AST-11 | **COMPLETE** | `programs/slawth-vawlt/src/raydium.rs:97-129,190-210`; `instructions/add_market.rs:65-82`; `instructions/compound.rs:173-226`; `tests/test_admin.rs:170-185`; `tests/test_compound.rs:542-558`; `tests/test_cpmm.rs:151-189` | The original short/stale-history fail-open is closed, but quiet pools now halt compounds and separate same-pool manipulation weaknesses remain in AST-20 through AST-22. |
| AST-12 | **COMPLETE** | `src/jupiter.rs:35-40,45-69`; `instructions/compound.rs:160-171,231-252`; `tests/test_compound.rs:511-537`; `scripts/vawlt/lib.mjs:470-484`; slots 3–5 agree with Jupiter’s [official route IDL](https://github.com/jup-ag/instruction-parser/blob/main/src/idl/jupiter.ts) | A future Jupiter upgrade remains an external dependency, and slot 0 `tokenProgram` is not defensively bound even though no current exploit was established. |
| AST-13 | **PARTIAL** | Exact one-transfer fee math is at `instructions/compound.rs:68-78,185-237`, while the mock always makes one output transfer at `test-programs/mock-router/src/lib.rs:28-80`; the probe at `scripts/dev/jupiter-trace-probe.mjs:21-34` is only a top-level unsigned simulation | The evidence supports two single-leg routes but does not prove transfer count, fee-cap behavior, or quote semantics for an actual Slawth CPI, split route, multihop route, or future adapter. |
| AST-14 | **PARTIAL** | `scripts/keeper/keeper.mjs:42-65`; `scripts/keeper/logic.mjs:15-25`; `scripts/test/keeper-logic.test.mjs:65-71` | Ordinary confirmation and `meta.err` failures are caught, but a null post-read count or null metadata fails open and the counter read is not anchored to the transaction slot or finality. |
| AST-15 | **COMPLETE** | `src/raydium.rs:134-166`; `instructions/add_market.rs:76-82`; `instructions/compound.rs:205-208`; `tests/test_cpmm.rs:195-219` | Creator-fee-enabled pools are rejected twice, with residual dependence on the deployed Raydium layout and future upgrades. |
| AST-16 | **COMPLETE** | `src/raydium.rs:176-210,295-308`; `tests/test_cpmm.rs:222-252`; the endpoint behavior matches Raydium’s [official oracle implementation](https://github.com/raydium-io/raydium-cp-swap/blob/master/programs/cp-swap/src/states/oracle.rs) | The code does not reject a future-valued `last_update_timestamp`, and the packet does not tie the cited upstream source or offset to a hash of the deployed Raydium binary. |
| AST-17 | **ACCEPTED-WITH-RATIONALE** | `scripts/vawlt/lib.mjs:513-531,574-591`; `scripts/keeper/config.json:9`; `instructions/compound.rs:33-35,239-252` | User and fee-vault funds remain protected, but the swap PDA can own bounded auxiliary ATAs whose donated or stranded balances can later be consumed by a route. |
| AST-18 | **PARTIAL** | `scripts/keeper/logic.mjs:8-12`; `scripts/test/keeper-logic.test.mjs:55-62`; `scripts/vawlt/lib.mjs:539-591` | Positive minima are reached, but an exception during the optional surplus attempt discards an already-valid contribution-only plan, and a permitted zero minimum never reaches the true legal minimum of one raw unit. |
| AST-19 | **COMPLETE** | `instructions/admin.rs:56-69`; `tests/test_admin.rs:190-207` | The fee is monotonic, although the admin retains authority over slippage, market parameters, and pausing. |
| AST-01 | **PARTIAL** | `src/math.rs:32-61`; `src/raydium.rs:97-129,190-210`; `instructions/compound.rs:173-237` | The guard is now fail-closed, but pricing still relies on the same venue without independent price, minimum-liquidity, maximum-impact, or fixed-point-precision bounds. |
| AST-03 | **MISSING** | `instructions/initialize.rs:8-14,23-34`; `instructions/enroll.rs:46-66`; `tests/test_enroll.rs:8-23` | Initialization authenticates rather than revokes the upgrade authority, while each enrollment still delegates `u64::MAX`. |
| AST-05 | **MISSING** | `src/jupiter.rs:20-31`; `scripts/vawlt/lib.mjs:463-468`; synthetic suffix tests at `src/jupiter.rs:86-128` | Both parsers still trust the final 19 bytes without canonically decoding `routePlan` through EOF, so Slawth and Jupiter can interpret different arguments even though postconditions limit the damage. |
| AST-06 | **PARTIAL** | `src/math.rs:32-49`; `instructions/compound.rs:185-203`; `scripts/vawlt/lib.mjs:539-563` | CLMM output remains a linear spot multiplication without tick traversal, liquidity, price impact, or dynamic-fee modeling, so executable-sized routes can fail closed. |
| AST-08 | **COMPLETE** | `scripts/vawlt/lib.mjs:513-531,574-591`; `scripts/keeper/config.json:9` | Arbitrary setup instructions and foreign signers are closed, leaving bounded rent exposure for the allowlisted mint/program combinations and no included unit test for this safety-critical parser. |
| AST-09 | **PARTIAL** | `instructions/add_market.rs:53-93`; `instructions/compound.rs:82-86,179-218`; `src/raydium.rs:22-34` | Discriminators are now rechecked, but CPMM vaults are raw-decoded without validating initialized/frozen state or their internal authority. |
| F2 | **COMPLETE** | `src/raydium.rs:97-129,190-210`; `instructions/add_market.rs:65-82`; `instructions/compound.rs:173-226` | The specific fail-open and short-window defects are fixed, while the broader same-venue manipulation risk remains under AST-01 and AST-20 through AST-22. |
| F4 | **MISSING** | Same evidence as AST-03: `instructions/initialize.rs:8-14`; `instructions/enroll.rs:46-53` | Every approved reward balance remains exposed to malicious upgraded code until the on-chain loader authority is actually removed. |
| F7 | **PARTIAL** | `instructions/enroll.rs:56-111`; `tests/test_enroll.rs:8-23,64-85,119-136` | `reapprove` preserves the floor, but true unenroll/re-enroll or enrollment in a replacement market snapshots the entire current balance and requires an explicit user `set_floor` correction. |
| F8 | **PARTIAL** | Same evidence as AST-06: `src/math.rs:32-49`; `instructions/compound.rs:190-203`; `scripts/vawlt/lib.mjs:539-563` | Partial sizing reduces failures but does not make the CLMM reference calculation executable-size-aware. |
| F9 | **MISSING** | `instructions/compound.rs:106-152,254-265`; `tests/test_compound.rs:445-462`; `scripts/keeper/keeper.mjs:94-125` | Token-2022 fees make the vault receive less than recorded `total_fees` and emitted `fee`, while keeper profitability and delivery metrics use gross fee and quoted rather than realized amounts. |

## New findings

Only AST-23 is demonstrably created by the v1.6 remediation interaction. AST-20 through AST-22 are newly identified latent CPMM issues that appear to predate v1.6, while AST-24 is confirmed in the newly complete client but cannot be dated from the incomplete v1.4 packet.

### AST-20 — High, conditional — Permissionless unbounded CPMM sizing collapses the execution floor

**File and line:** `programs/slawth-vawlt/src/instructions/compound.rs:89-103,205-237`; `programs/slawth-vawlt/src/math.rs:51-61`; `programs/slawth-vawlt/tests/test_cpmm.rs:105-129`

**Description:** Any caller chooses any `amount` up to the victim’s complete balance above the floor, while the CPMM reference output includes the full constant-product impact of that caller-selected size. There is no on-chain maximum input-to-reserve ratio, maximum price impact, or secondary linear/TWAP floor.

**Concrete scenario:** With `amount ≈ 9 × reserve_in`, the reference output is approximately `0.9 × reserve_out`, roughly one-tenth of the pool’s pre-trade spot value for that input. A malicious caller can select the victim’s entire pending balance and route through a poor or attacker-controlled Jupiter-supported venue that delivers only the collapsed threshold while capturing the difference.

**Code-level fix:** Enforce a per-market maximum amount and `venue_in / reserve_in` or maximum-impact bound on-chain, require minimum reference liquidity, and retain a spot/TWAP-linear secondary floor that cannot collapse with caller-selected size.

**Status:** **CONFIRMED.** The vulnerable arithmetic and permissionless amount choice are explicit; both proposed launch markets are CLMM, so this is dormant until a CPMM market is enabled.

### AST-21 — High, conditional — An idle CPMM TWAP can be backfilled at a donated reserve price

**File and line:** `programs/slawth-vawlt/src/instructions/compound.rs:205-226`; `programs/slawth-vawlt/src/raydium.rs:190-210`; `programs/slawth-vawlt/tests/test_cpmm.rs:151-190,222-252`

**Description:** Slawth derives both spot and executable reserves from current vault balances. Raydium’s oracle update accumulates the supplied current price over the elapsed time since its preceding update; therefore, as an inference from the [official update implementation](https://github.com/raydium-io/raydium-cp-swap/blob/master/programs/cp-swap/src/states/oracle.rs), the first swap after a quiet interval can attribute that whole interval to a newly manipulated reserve price.

**Concrete scenario:** An attacker with a recoverable LP position donates reward tokens into a shallow, idle CPMM vault and submits a dust swap. The update can backfill more than the 900-second window at the depressed price, after which both the newly “fresh” TWAP and current spot agree and an immediate victim compound accepts the depressed floor.

**Code-level fix:** Do not permit CPMM references without an independent oracle and minimum liquidity; at minimum reject excessive gaps between observations, cap input impact, and add a real-Ray­dium donation → first post-idle swap → victim-compound adversarial test.

**Status:** **PLAUSIBLE.** Current upstream source supports the update rule, but the packet contains no adversarial invocation of the deployed Raydium binary and no binary hash tying that source to mainnet.

### AST-22 — Medium, conditional — Q32.32 CPMM prices can round the deviation guard to zero

**File and line:** `programs/slawth-vawlt/src/raydium.rs:195-210`; `programs/slawth-vawlt/src/instructions/add_market.rs:76-82`; `programs/slawth-vawlt/src/instructions/compound.rs:220-226`

**Description:** Registration accepts any `Some(twap)` including zero, and both spot and TWAP use truncated Q32.32 ratios. When `reserve_out / reserve_in < 2^-32`, both values can be zero and `0 >= 0` passes regardless of economically material reserve changes.

**Concrete scenario:** For `reserve_in = 10^19` and `reserve_out = 10^9`, Q32.32 spot is zero, but an input of `10^18` still produces roughly `9.09 × 10^7` output units under the constant-product calculation. The supposedly active deviation guard therefore observes no price at all.

**Code-level fix:** Reject zero or insufficient-resolution oracle prices at registration and compound, enforce supported reserve/decimal ratios, and compare with higher-precision cross-products rather than a prematurely truncated Q32 quotient.

**Status:** **CONFIRMED.** The arithmetic path is deterministic, although applicability depends on a future CPMM configuration.

### AST-23 — Low — Landed custom errors bypass keeper backoff

**File and line:** `scripts/vawlt/lib.mjs:52-64`; `scripts/keeper/logic.mjs:19-23`; `scripts/keeper/keeper.mjs:115-120`; `scripts/test/keeper-logic.test.mjs:65-70`

**Description:** `assertLanded` throws confirmation errors as decimal JSON such as `{"Custom":6011}`, while `isSlippageFailure` recognizes names or hexadecimal `0x177b`/`0x1780`, not decimal `6011`/`6016`. Because the confirmation branch throws first, fetched program logs containing `PriceDeviates` are not appended.

**Concrete scenario:** Repeated landed `SlippageExceeded` or `PriceDeviates` failures are recorded as failures but never advance backoff, so the keeper resubmits every ten minutes and burns SOL until its minimum-balance guard stops automation.

**Code-level fix:** Decode `InstructionError.Custom` structurally through `ERR`, or recognize decimal 6011/6016 and attach fetched logs before classification; add an end-to-end test from `assertLanded` through `Backoff.fail`.

**Status:** **CONFIRMED.** Direct reproduction shows both decimal custom-error strings fail the supplied matcher while the named pre-check error matches.

### AST-24 — Low — The generic client reports failed landed transactions as confirmed

**File and line:** `scripts/vawlt/lib.mjs:609-624`

**Description:** `dryRunOrSend` checks simulation errors, but its send path discards `confirmTransaction(...).value.err`, does not fetch transaction metadata, prints `confirmed`, and returns the signature.

**Concrete scenario:** A configuration, enrollment, or manual compound transaction lands with a program error; the CLI prints a Solscan “confirmed” message and the operator proceeds under the false assumption that the requested state transition occurred.

**Code-level fix:** Reject a non-null confirmation error, fetch and require non-null error-free metadata, verify the expected state transition, and require `finalized` status for proof or authority-burn gates.

**Status:** **CONFIRMED.** Its presence in v1.6 is certain, but the incomplete v1.4 client does not establish when it was introduced.

The index-seeded on-chain derivation itself is coherent: creation atomically uses and increments `config.market_count` (`instructions/add_market.rs:17-25,95-115`), the index is stored (`src/state.rs:18-19,53-55`), every later instruction rederives from it, and `tests/test_admin.rs:149-164` exercises old-index rejection and next-index success. It is not a migration mechanism: old enrollments remain tied to the old market, and `scripts/vawlt/lib.mjs:113-121,316-323` can still let one missing legacy CPMM reference abort discovery before `keeper.mjs:77` filters paused markets, so replacement-market operations should isolate hydration errors per market.

## Insufficient live evidence

- The AST-13 simulations are top-level Jupiter transactions with `sigVerify:false`, not submitted Slawth → Jupiter CPIs signed by the market swap PDA. They have no signature, slot, raw transaction, resolved ALT account list, complete API response, route bytes, or archival logs.

- Both observed AST-13 routes are single-leg point samples, and the 10,000-raw ZEC input is below the configured 50,000-raw minimum. They do not exercise split routes, multihop routes, fee-cap binding, amount-dependent route selection, or the actual proof amount.

- `jupiter-trace-probe.mjs:21-34` requests only slot 4’s simulated account state and therefore cannot prove slot 3 was untouched. Its transfer counts are global log-string counts rather than decoded Token-2022 instructions tied to a mint, source, destination, and gross amount.

- No active target-mint `TransferFeeConfig` is preserved with epoch, basis points, maximum fee, and cap-binding arithmetic, and no destination withheld-fee delta is supplied. Destination base delta equaling the API quote strongly supports net-quote semantics for those exact simulations but does not establish a general Jupiter invariant.

- The route-slot evidence is consistent with Jupiter’s current official IDL and supports AST-12, but the packet does not preserve the raw historical route accounts or tie the IDL to the deployed Jupiter binary at an identified slot.

- The AST-16 live read establishes that byte 4043 was 13 seconds later than the newest entry in one account, but correlation is not an execution trace. The integration test patches fixture bytes rather than invoking the real CPMM program, and neither the current upstream source nor the stated layout is tied to a deployed-binary hash.

- The two live CLMM ring reads prove momentary freshness and coverage only; they establish neither continuing availability nor manipulation cost or minimum liquidity.

- The packet states that it was generated at `2026-09-08T20:00Z` while describing live observations gathered at approximately `21:00Z` that day. That chronology needs corrected, reproducible provenance.

- The workspace contains the source and tests only inside Markdown, not as a Cargo/Node workspace with fixtures and mock binaries. Consequently, the packet’s claim that all 65 tests pass could not be independently rerun.

- v1.6 is not deployed, so there is no deployed ELF hash, `ProgramData` state, initialization/configuration trace, or proof that the eventual deployment exactly matches the reviewed source.

## Exact first-compound trace required for AST-13

The first trace should be a finalized ZEC → ZCAT Slawth compound at or above 50,000 raw ZEC while ZCAT’s transfer fee is active. Preserve all of the following:

1. **Provenance and finality:** transaction signature, slot, block time, complete raw RPC response, `meta.err == null`, and finalized status.

2. **Executable identity:** a reproducible hash of the deployed Slawth ELF matching this reviewed v1.6 source, its `ProgramData` account and retained upgrade authority, plus the program IDs and ProgramData/binary identities of Jupiter and every invoked venue at that slot.

3. **Complete instruction:** the outer `compound(amount, jupiter_data)`, the complete v0 message, every resolved ALT address, and a canonical Borsh decode of the Jupiter `routePlan` and trailing arguments through EOF.

4. **Fixed-account mapping:** prove Jupiter slot 0 is the intended source token program; slot 1 is the market swap PDA; slot 2 is transit; slot 3 is the canonical swap-PDA ZCAT ATA; slot 4 is the enrolled user’s canonical ZCAT ATA; slot 5 is the ZCAT mint; and slot 6 is the Jupiter sentinel with no platform fee.

5. **Decoded inner instruction tree:** enumerate every Token-2022 `Transfer` or `TransferChecked` involving ZCAT inside the Jupiter subtree, including source, destination, mint, authority, and gross amount rather than relying on log counts.

6. **Exact account deltas:** obtain transaction-consistent pre/post raw account data for the user reward ATA, transit, fee vault, slot 3, slot 4, and every other swap-PDA-owned account used by the route, including both base amounts and Token-2022 withheld-fee extensions.

7. **Fee arithmetic:** preserve ZCAT’s epoch-selected transfer-fee basis points and maximum fee, state whether the cap binds, and prove  
   `slot4 base delta = gross final transfer − calculate_epoch_fee(gross final transfer) = Compounded.target_out`,  
   with slot 3 unchanged and its withheld amount unchanged.

8. **Program postconditions:** show the expected transit remainder, the actual gross/net fee-vault receipt, the `Compounded` event, and exact `+1` enrollment and market counter changes at or after the transaction slot.

9. **Quote classification:** preserve the Jupiter API quote and decoded `quoted_out_amount`, then state explicitly whether each is gross or net by comparing it with the decoded transfer and destination delta; favorable execution must not be used to infer transfer count.

A merely successful compound does not settle AST-13 because favorable venue execution could absorb an unmodeled second fee. One fully decoded trace settles the exact adapter and route shape observed; before immutability, repeat it for the other launch market and every route family that remains permitted, or constrain `routePlan` on-chain to the proven shape.

No files were modified. Review discipline used independent parallel source reviews under dispatching-parallel-agents and fresh evidence checks under verification-before-completion.