# ALEX AMM pool v2 static-analysis report

Contract: `SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.amm-pool-v2-01`

Source reviewed: `alex.clar`, 622 lines

Source SHA-256: `68F855EBB454F132D7FB8B0F1FA0601D4D870679FF749C40E0C6FD0EC93C8DC8`

Scope note: this report reviews the AMM pool/router contract source supplied in the bounty. The contract delegates registry state to `.amm-registry-v2-01`, token custody to `.amm-vault-v2-01`, and LP token accounting to `.token-amm-pool-v2-01`; where an invariant is delegated, I call that out explicitly.

Responsible disclosure: no high or critical findings were identified in this static review, so no private disclosure was required before public submission.

## 1. State model

### Local state

| Store | Line | Type | Initial value | Mutators | Authority |
|---|---:|---|---|---|---|
| `paused` | 16 | `bool` | `true` | `pause` | DAO principal `.executor-dao` or approved DAO extension |

### Delegated state and custody

| External contract | Role | Called from | Notes |
|---|---|---|---|
| `.amm-registry-v2-01` | Pool registry, pool balances, parameters, block window, oracle fields, fee settings, blocklist, max ratio/switch thresholds | read-only passthroughs at lines 19-28, setters at 202-251, pool create/update at 255, 275, 298, 328, 357 | This contract is the source of truth for pool details. This reviewed contract trusts registry setters to enforce several numeric bounds unless duplicated locally. |
| `.amm-vault-v2-01` | Custody for token transfers and reserves | liquidity remove at 297, swaps at 326-327 and 355-356 | User deposits call the token trait directly to transfer to the vault. Withdrawals/reserve accounting are executed `as-contract` against the vault. |
| `.token-amm-pool-v2-01` | LP token balance, mint and burn | balance read at 286, mint at 276, burn at 299 | LP token operations are `as-contract` except the balance read. |
| `.executor-dao` | DAO authorization and extension lookup | `is-dao-or-extension` at 17-18, used by pause and setters | Direct DAO calls authorize by `tx-sender`; extension calls authorize by `contract-caller`. |

### Constants

Error constants at lines 2-15: `ERR-NOT-AUTHORIZED`, `ERR-POOL-ALREADY-EXISTS`, `ERR-INVALID-POOL`, `ERR-BLOCKLISTED`, `ERR-INVALID-LIQUIDITY`, `ERR-PERCENT-GREATER-THAN-ONE`, `ERR-EXCEEDS-MAX-SLIPPAGE`, `ERR-ORACLE-NOT-ENABLED`, `ERR-ORACLE-AVERAGE-BIGGER-THAN-ONE`, `ERR-PAUSED`, `ERR-SWITCH-THRESHOLD-BIGGER-THAN-ONE`, `ERR-NO-LIQUIDITY`, `ERR-MAX-IN-RATIO`, `ERR-MAX-OUT-RATIO`.

Math constants at lines 481-527: `ONE_8`, `MAX_POW_RELATIVE_ERROR`, `UNSIGNED_ONE_8`, exponent bounds, approximation coefficient lists, and math error constants `ERR-X-OUT-OF-BOUNDS`, `ERR-Y-OUT-OF-BOUNDS`, `ERR-PRODUCT-OUT-OF-BOUNDS`, `ERR-INVALID-EXPONENT`, `ERR-OUT-OF-BOUNDS`.

## 2. Function inventory

### Authorization/read helpers

| Function | Lines | Authority | Preconditions/asserts | State mutations | External calls/transfers |
|---|---:|---|---|---|---|
| `is-dao-or-extension` | 17-18 | DAO or DAO extension | Asserts `tx-sender == .executor-dao` or `.executor-dao is-extension contract-caller` | None | `.executor-dao is-extension` |
| `is-blocklisted-or-default` | 19-20 | Open read | None local | None | Registry blocklist |
| `get-switch-threshold` | 21-22 | Open read | None local | None | Registry threshold |
| `get-pool-details-by-id` | 23-24 | Open read | None local | None | Registry pool lookup |
| `get-pool-details` | 25-26 | Open read | None local | None | Registry pool lookup |
| `get-pool-exists` | 27-28 | Open read | None local | None | Registry existence lookup |
| `is-paused` | 29-30 | Open read | None | None | None |
| `get-max-ratio-limit` | 196-197 | Open read | None local | None | Registry max-ratio lookup |

### Pool parameter/status reads

| Function | Lines | Authority | Preconditions/asserts | State mutations | External calls/transfers |
|---|---:|---|---|---|---|
| `get-balances` | 31-35 | Open read | Pool must exist | None | Registry via `get-pool-details` |
| `get-start-block` | 36-37 | Open read | Pool must exist | None | Registry |
| `get-end-block` | 38-39 | Open read | Pool must exist | None | Registry |
| `get-max-in-ratio` | 40-41 | Open read | Pool must exist | None | Registry |
| `get-max-out-ratio` | 42-43 | Open read | Pool must exist | None | Registry |
| `check-pool-status` | 44-48 | Open read | `block-height` inside `[start-block, end-block]` | None | Registry |
| `get-oracle-enabled` | 49-50 | Open read | Pool must exist | None | Registry |
| `get-oracle-average` | 51-52 | Open read | Pool must exist | None | Registry |
| `get-oracle-resilient` | 53-60 | Open read | Pool must exist in either orientation; oracle enabled | None | Registry and `get-oracle-instant` |
| `get-oracle-instant` | 61-69 | Open read | Pool must exist in either orientation; oracle enabled | None | Registry and internal price math |
| `get-price` | 70-74 | Open read | Pool must exist | None | Registry and internal price math |
| `get-threshold-x` | 75-76 | Open read | Pool must exist | None | Registry |
| `get-threshold-y` | 77-78 | Open read | Pool must exist | None | Registry |
| `get-fee-rebate` | 79-80 | Open read | Pool must exist | None | Registry |
| `get-fee-rate-x` | 81-82 | Open read | Pool must exist | None | Registry |
| `get-fee-rate-y` | 83-84 | Open read | Pool must exist | None | Registry |
| `get-pool-owner` | 85-86 | Open read | Pool must exist | None | Registry |

### Quote/read-only math functions

| Function | Lines | Authority | Preconditions/asserts | State mutations | External calls/transfers |
|---|---:|---|---|---|---|
| `get-y-given-x` | 87-97 | Open read | Pool exists; `dx < balance-x * max-in-ratio`; output below max-out-ratio | None | Registry; private math |
| `get-x-given-y` | 98-108 | Open read | Pool exists; `dy < balance-y * max-in-ratio`; output below max-out-ratio | None | Registry; private math |
| `get-y-in-given-x-out` | 109-119 | Open read | Pool exists; requested output/input within max ratios | None | Registry; private math |
| `get-x-in-given-y-out` | 120-130 | Open read | Pool exists; requested output/input within max ratios | None | Registry; private math |
| `get-x-given-price` | 131-136 | Open read | Pool exists; requested price below current internal price | None | Registry; private math |
| `get-y-given-price` | 137-142 | Open read | Pool exists; requested price above current internal price | None | Registry; private math |
| `get-token-given-position` | 143-149 | Open read | Pool exists; positive `dx`; positive explicit/default `dy` | None | Registry; private math |
| `get-position-given-mint` | 150-155 | Open read | Pool exists; `total-supply > 0` | None | Registry; private math |
| `get-position-given-burn` | 156-161 | Open read | Pool exists; `total-supply > 0` | None | Registry; private math |
| `get-helper`, `get-helper-a/b/c` | 162-177 | Open read | Underlying pool route lookups and quote asserts must pass | None | Registry and quote functions |
| `fee-helper`, `fee-helper-a/b/c` | 178-191 | Open read | Underlying fee lookups must pass | None | Registry |
| `get-invariant` | 192-195 | Open read | Private math bounds must hold | None | Registry threshold and private math |

### Public admin/mutating functions

| Function | Lines | Authority | Preconditions/asserts | State mutations | External calls/transfers |
|---|---:|---|---|---|---|
| `pause` | 198-201 | DAO or DAO extension | `is-dao-or-extension` must pass | Sets local `paused` | None |
| `set-start-block` | 202-206 | Pool owner by `tx-sender`, or DAO/extension | Pool exists; caller authorized | Delegated registry mutation | `as-contract` registry `set-start-block` |
| `set-end-block` | 207-211 | Pool owner by `tx-sender`, or DAO/extension | Pool exists; caller authorized | Delegated registry mutation | `as-contract` registry `set-end-block` |
| `set-max-in-ratio` | 212-216 | Pool owner by `tx-sender`, or DAO/extension | Pool exists; caller authorized | Delegated registry mutation | `as-contract` registry `set-max-in-ratio` |
| `set-max-out-ratio` | 217-221 | Pool owner by `tx-sender`, or DAO/extension | Pool exists; caller authorized | Delegated registry mutation | `as-contract` registry `set-max-out-ratio` |
| `set-oracle-enabled` | 222-226 | Pool owner by `tx-sender`, or DAO/extension | Pool exists; caller authorized | Delegated registry mutation | `as-contract` registry `set-oracle-enabled` |
| `set-oracle-average` | 227-231 | Pool owner by `tx-sender`, or DAO/extension | Pool exists; caller authorized | Delegated registry mutation | `as-contract` registry `set-oracle-average` |
| `set-threshold-x` | 232-236 | Pool owner by `tx-sender`, or DAO/extension | Pool exists; caller authorized | Delegated registry mutation | `as-contract` registry `set-threshold-x` |
| `set-threshold-y` | 237-241 | Pool owner by `tx-sender`, or DAO/extension | Pool exists; caller authorized | Delegated registry mutation | `as-contract` registry `set-threshold-y` |
| `set-fee-rate-x` | 242-246 | Pool owner by `tx-sender`, or DAO/extension | Pool exists; caller authorized | Delegated registry mutation | `as-contract` registry `set-fee-rate-x` |
| `set-fee-rate-y` | 247-251 | Pool owner by `tx-sender`, or DAO/extension | Pool exists; caller authorized | Delegated registry mutation | `as-contract` registry `set-fee-rate-y` |

### Public liquidity and swap functions

| Function | Lines | Authority | Preconditions/asserts | State mutations | External calls/transfers |
|---|---:|---|---|---|---|
| `create-pool` | 252-256 | Open to non-blocklisted user | `tx-sender` not blocklisted; registry create must succeed; then `add-to-position` checks paused/liquidity/slippage | Registry creates pool; then add-position updates registry and LP supply | `as-contract` registry create; calls `add-to-position` |
| `add-to-position` | 257-278 | Open user path | Not paused; positive `dx` and computed `dy`; `max-dy >= dy` | Updates registry balances/supply; mints LP tokens | Transfers token X/Y from sender to vault; `as-contract` registry update; `as-contract` LP mint |
| `reduce-position` | 279-301 | Open LP holder path | Not blocklisted; not paused; `percent <= ONE_8`; LP balance read and burn quote must succeed | Updates registry balances/supply; burns LP tokens | `unwrap-panic` LP balance read; `as-contract` vault transfers X/Y to sender; registry update; LP burn |
| `swap-x-for-y` | 302-330 | Open trader path | Not blocklisted; not paused; pool active; `dx > 0`; price monotonicity assert; `min-dy <= dy`; quote max ratios must pass | Updates registry balances and oracle; adds fee reserve | Token X from sender to vault; optional vault token Y to sender; vault reserve add; registry update |
| `swap-y-for-x` | 331-359 | Open trader path | Not blocklisted; not paused; pool active; `dy > 0`; price monotonicity assert; `min-dx <= dx`; quote max ratios must pass | Updates registry balances and oracle; adds fee reserve | Token Y from sender to vault; optional vault token X to sender; vault reserve add; registry update |
| `swap-helper` | 360-363 | Open trader path | Uses existing orientation if found, otherwise reversed orientation must exist | Delegates to one swap | Delegates token movement to selected swap |
| `swap-helper-a` | 364-365 | Open trader path | First hop succeeds; second hop final `min-dz` check | Delegates to swaps | Two-hop route |
| `swap-helper-b` | 366-370 | Open trader path | First two hops succeed; third hop final `min-dw` check | Delegates to swaps | Three-hop route |
| `swap-helper-c` | 371-375 | Open trader path | First route segment succeeds; final segment with `min-dv` check | Delegates to swaps | Four-hop route implemented as two `swap-helper-a` calls |

### Private math

Private math functions at lines 376-622 implement price, swap curves, liquidity-share math, fixed-point multiplication/division, power/log/exp helpers, and approximation folds. The notable review points are: denominator checks are implicit in call-site invariants for many `div-*` calls, and `pow-down`/`pow-up` convert `pow-fixed` errors into panics with `unwrap-panic`.

## 3. Post-condition coverage matrix

| Public function | Token movements | Suggested post-conditions for caller |
|---|---|---|
| `pause` | None | No token post-conditions needed. Governance should verify expected admin principal and emitted transaction result. |
| `set-start-block` / `set-end-block` | None | No token post-conditions. Pool owner/DAO should verify the intended pool tuple and new block value in transaction arguments. |
| `set-max-in-ratio` / `set-max-out-ratio` | None | No token post-conditions. Admin should verify ratio bounds off-chain or through registry-side asserts. |
| `set-oracle-enabled` / `set-oracle-average` | None | No token post-conditions. Admin should verify oracle-average bounds and intended orientation. |
| `set-threshold-x` / `set-threshold-y` | None | No token post-conditions. Admin should verify threshold bounds before signing. |
| `set-fee-rate-x` / `set-fee-rate-y` | None | No token post-conditions. Admin should verify fee rate and fee rebate invariants in registry state. |
| `create-pool` | Transfers `dx` token X and computed `dy` token Y from sender to vault; mints LP token to sender | Require exact maximum debits for token X/Y from sender; require LP token mint at least expected amount; optionally require no other FT/NFT movement. |
| `add-to-position` | Transfers `dx` token X and computed `dy` token Y from sender to vault; mints LP token to sender | Require exact/maximum token X debit; maximum token Y debit no greater than `max-dy`; require LP mint at least quoted amount. |
| `reduce-position` | Transfers computed X/Y from vault to sender; burns LP token from sender | Require LP burn no greater than expected share amount; require minimum token X/Y credits to sender; optionally require vault transfers only for the selected token contracts. |
| `swap-x-for-y` | Transfers full `dx` token X from sender to vault; transfers `dy` token Y from vault to sender when `dy > 0`; adds fee reserve | Require exact token X debit of `dx`; require token Y credit at least `min-dy`; optionally prohibit any other token debits from sender. |
| `swap-y-for-x` | Transfers full `dy` token Y from sender to vault; transfers `dx` token X from vault to sender when `dx > 0`; adds fee reserve | Require exact token Y debit of `dy`; require token X credit at least `min-dx`; optionally prohibit any other token debits from sender. |
| `swap-helper` | Same as selected one-hop swap, orientation-dependent | Require exact input debit for the trait supplied as the route input and minimum output credit for the intended output token; verify route orientation off-chain. |
| `swap-helper-a` | Two sequential swaps; first hop has no explicit min output, final hop checks `min-dz` | Require exact first input debit and final output credit at least `min-dz`; consider a route simulator before signing because intermediate hop output is not separately bounded. |
| `swap-helper-b` | Three sequential swaps; intermediate hops have no explicit min output, final hop checks `min-dw` | Require exact first input debit and final output credit at least `min-dw`; no post-condition can directly enforce all intermediate prices, so use route simulation and final output post-condition. |
| `swap-helper-c` | Four sequential swaps via two two-hop segments; intermediate hops have no explicit min output, final hop checks `min-dv` | Require exact first input debit and final output credit at least `min-dv`; use route simulation and narrow transaction validity where possible. |

## 4. Authority/access-control matrix

| Action/function | Authorized principals | Mechanism | Notes |
|---|---|---|---|
| Global pause/unpause | `.executor-dao` or DAO extension | `is-dao-or-extension` at lines 17-18, used in `pause` | Local kill switch blocks liquidity and swap user paths but does not block read-only queries or admin setters. |
| Pool parameter setters | Pool owner by `tx-sender`, `.executor-dao`, or DAO extension | `asserts! (or (is-eq tx-sender pool-owner) (is-ok (is-dao-or-extension)))` at lines 205, 210, 215, 220, 225, 230, 235, 240, 245, 250 | Pool-owner branch uses `tx-sender`; DAO extension branch uses `contract-caller`. This should be intentional and documented. |
| Pool creation | Any non-blocklisted sender | Blocklist check at line 254 plus registry create | Initial `paused` value is `true`, so pool creation only proceeds to initial liquidity when global pause is false because `add-to-position` checks pause. |
| Add liquidity | Any user | Pause and liquidity/slippage checks at 270-272 | No blocklist check in `add-to-position`; blocklist only applies to `create-pool`, `reduce-position`, and swaps. If registry/vault is expected to block deposits, this is delegated. |
| Reduce liquidity | Any LP holder not blocklisted | Blocklist, pause, percent check at 294-296 | Uses LP balance from LP token contract and burns after vault transfer and registry update in transaction order. Clarity rollback protects atomicity on later failure. |
| Swap | Any non-blocklisted sender | Blocklist, pause, active block window, positive input, slippage at 319-324 and 348-353 | User should still attach post-conditions for exact input and minimum output. |

## 5. Clarity best-practice review

| Topic | Result |
|---|---|
| `tx-sender` vs `contract-caller` | Pool-owner admin setters authorize by `tx-sender` (lines 205, 210, 215, 220, 225, 230, 235, 240, 245, 250). This allows owner-originated contract calls to exercise owner authority. That may be intended for account contracts, but it should be explicit because `contract-caller` would be stricter. DAO extension authorization correctly uses `contract-caller` through `.executor-dao is-extension`. |
| `unwrap-panic` / `unwrap-err-panic` | `reduce-position` uses `unwrap-panic` for LP balance at line 286. `pow-down` and `pow-up` use `unwrap-panic` on `pow-fixed` at lines 496 and 502. User-facing quote/swap paths can reach the math wrappers. Prefer converting these to typed errors where possible. |
| Arithmetic overflow risk in `*` / `+` | The contract uses many fixed-point `*` and `+` operations in quote and math paths. Clarity aborts on overflow, so this is mainly denial-of-service/revert risk rather than silent corruption. `pow-fixed` includes upper-bound asserts at lines 605-606, but wrappers panic rather than returning the typed math errors. |
| `as-contract` usage / principal escalation | Used for registry, vault, reserve, and LP token operations at lines 206, 211, 216, 221, 226, 231, 236, 241, 246, 251, 255, 275-276, 297-299, 326-328, 355-357. This is consistent with delegated registry/custody design but increases reliance on external contract authorization being limited to this pool contract. |
| Trait conformance gaps | Public user paths accept `<ft-trait>` tokens and use `contract-of` for registry lookup. They rely on SIP-010 trait conformance and on registry entries matching the intended token principals. Post-conditions remain important because trait-conforming tokens can still have token-specific behavior. |
| Pause coverage | Pause gates add/remove liquidity and swaps but not admin setters or read-only helpers. This is reasonable for a kill switch if admin remediation must remain available. |
| Blocklist coverage | `create-pool`, `reduce-position`, and swaps check blocklist. `add-to-position` does not. If the design intends to block listed users from increasing liquidity, add a check at line 270. |

## 6. Findings table

| ID | Severity | Function | Line | Finding | Recommended fix |
|---|---|---|---:|---|---|
| ALEX-01 | Low | `add-to-position` | 270 | Blocklisted principals can add liquidity directly because `add-to-position` checks pause/liquidity/slippage but does not check `is-blocklisted-or-default tx-sender`; only `create-pool`, `reduce-position`, and swaps enforce the blocklist. | Add `asserts! (not (is-blocklisted-or-default tx-sender)) ERR-BLOCKLISTED` to `add-to-position`, unless blocklisted LP deposits are intentionally allowed and documented. |
| ALEX-02 | Informational | Admin setters | 205, 210, 215, 220, 225, 230, 235, 240, 245, 250 | Pool-owner authorization uses `tx-sender`, so a contract call initiated by the pool owner can mutate pool parameters. This is not automatically exploitable, but it is broader than `contract-caller` based authorization. | Document this as intentional account-contract compatibility, or switch the pool-owner branch to `contract-caller` if only direct owner calls should be allowed. |
| ALEX-03 | Low | `set-max-in-ratio`, `set-max-out-ratio`, `set-oracle-average`, `set-threshold-x/y`, `set-fee-rate-x/y` | 212-251 | Numeric bounds are not checked locally before delegating to registry setters. Constants such as `ERR-ORACLE-AVERAGE-BIGGER-THAN-ONE` and `ERR-SWITCH-THRESHOLD-BIGGER-THAN-ONE` exist in this contract, but the router does not enforce them itself. | Keep strong bounds in registry and add local defensive asserts for `<= ONE_8` where applicable, especially oracle average, fee rates, fee rebate, max ratios, and switch thresholds. |
| ALEX-04 | Low | `reduce-position`, `pow-down`, `pow-up` | 286, 496, 502 | `unwrap-panic` appears in user-reachable paths. LP balance read errors and math bound failures abort with a panic instead of typed contract errors. | Replace `unwrap-panic` with `try!` where return types allow it, or wrap errors into explicit `err` codes so callers and integrators can distinguish failed invariants from generic aborts. |
| ALEX-05 | Informational | `swap-x-for-y`, `swap-y-for-x` | 309-323, 338-352 | If configured fee rates allow the fee to consume the full input, net input becomes zero. The functions can then transfer input while output is zero when the caller supplies zero minimum output. This requires permissive fee configuration and/or a careless caller, but it is poor UX and easy to protect against. | Assert `dx-net-fees > 0` and `dy-net-fees > 0`, and keep fee-rate bounds below 100% in registry. Users should always set nonzero `min-dy`/`min-dx` and post-conditions. |
| ALEX-06 | Informational | `swap-helper-a/b/c` | 364-375 | Multi-hop helpers only pass a minimum output to the final hop; intermediate hops use `none`. Final output slippage still protects the user's final asset amount, but intermediate route quality is not explicitly bounded per hop. | Document this route behavior and recommend route simulation plus final-output post-conditions. Consider helper variants with per-hop minimums for integrators who need tighter route controls. |

## Finding details

### ALEX-01: `add-to-position` does not enforce blocklist

Severity: Low

Lines: 257-278

`create-pool` checks `is-blocklisted-or-default tx-sender` at line 254 before creating a pool and delegating to `add-to-position`. `reduce-position` checks the same at line 294, and swaps check at lines 319 and 348. Direct calls to `add-to-position`, however, only check `paused`, positive liquidity, and slippage at lines 270-272. If the blocklist is intended to prevent all economic participation by a listed address, a listed user can still increase a position while unpaused.

Impact is limited because this does not let the user withdraw while blocklisted and does not bypass swap blocklist checks. It can still leave unwanted liquidity participation and operational edge cases for later unblocking or administrative handling.

Recommended fix: add the blocklist assert to `add-to-position` before token transfers:

```clarity
(asserts! (not (is-blocklisted-or-default tx-sender)) ERR-BLOCKLISTED)
```

### ALEX-02: Pool-owner setters rely on `tx-sender`

Severity: Informational

Lines: 205, 210, 215, 220, 225, 230, 235, 240, 245, 250

Each pool parameter setter authorizes either the pool owner by `tx-sender` or the DAO/extension path. The DAO extension path is careful: `is-dao-or-extension` calls `.executor-dao is-extension contract-caller` at line 18. The pool-owner path is broader because an owner-originated contract call can carry the owner's `tx-sender` through a different `contract-caller`.

This can be a valid design for account abstraction or admin proxy workflows. The risk is mainly integration confusion: users may assume only a direct owner call can mutate pool settings.

Recommended fix: document the intended behavior. If admin operations should only be direct calls by the owner principal, authorize the owner branch with `contract-caller`.

### ALEX-03: Router setters depend on registry-side numeric bounds

Severity: Low

Lines: 212-251

The reviewed contract defines specific error constants for bound failures, including `ERR-ORACLE-AVERAGE-BIGGER-THAN-ONE` and `ERR-SWITCH-THRESHOLD-BIGGER-THAN-ONE`, but the public setters do not locally check the incoming numeric values. Instead, they authorize the caller and delegate the mutation to `.amm-registry-v2-01` with `as-contract`.

This is acceptable only if the registry enforces all required invariants. If a future registry upgrade or related setter misses a bound, this contract will pass through bad values. A bad oracle average, fee rate, threshold, or max-ratio can cause invalid quotes, zero-output swaps, or persistent reverts.

Recommended fix: duplicate critical bounds locally for defense in depth, especially values expected to be `<= ONE_8`.

### ALEX-04: `unwrap-panic` in user-reachable paths

Severity: Low

Lines: 286, 496, 502

`reduce-position` reads LP token balance with:

```clarity
(unwrap-panic (contract-call? .token-amm-pool-v2-01 get-balance-fixed ...))
```

The math wrappers `pow-down` and `pow-up` also unwrap `pow-fixed` with `unwrap-panic`. Quote and swap functions can reach these math wrappers through internal pricing and invariant calculations.

The transaction remains atomic, so this is not a direct funds-loss issue. The downside is diagnosability and composability: callers receive a panic instead of a typed domain error such as `ERR-NO-LIQUIDITY`, `ERR-X-OUT-OF-BOUNDS`, or `ERR-Y-OUT-OF-BOUNDS`.

Recommended fix: return typed errors from wrappers or propagate the underlying math errors with `try!` where signatures permit.

### ALEX-05: Zero net-input swaps are possible under permissive fee configuration

Severity: Informational

Lines: 309-323 and 338-352

`swap-x-for-y` computes `fee = mul-up dx fee-rate-x`, then sets `dx-net-fees` to zero when `dx <= fee`. `swap-y-for-x` mirrors this for Y. If fee configuration permits this state and the caller uses the default zero minimum output, the function can transfer input to the vault/reserve while returning zero output.

This is strongly mitigated by sane registry fee bounds and user-supplied `min-dy`/`min-dx` plus post-conditions. Still, adding a local net-input assert improves UX and protects integrations that accidentally pass `none` or zero minimum output.

Recommended fix: assert `dx-net-fees > 0` and `dy-net-fees > 0` after fee calculation, and enforce fee rates well below `ONE_8` in registry.

### ALEX-06: Multi-hop helpers lack per-hop minimums

Severity: Informational

Lines: 364-375

`swap-helper-a/b/c` pass `none` for intermediate hop minimum outputs and only enforce the minimum on the final asset. This protects the user's final output if the user supplies a nonzero final minimum and attaches post-conditions, but it does not let an integrator bound each intermediate hop.

Recommended fix: document this behavior and consider helper variants that accept per-hop minimum outputs for wallets/routers that want tighter execution constraints.

## Non-findings and positive observations

- User-facing liquidity and swap functions are transactional: token transfers, registry updates, reserve updates, and LP mint/burn happen in one Clarity transaction, so later failures revert earlier effects.
- Swap functions include explicit pause, active block-window, positive input, slippage, and max-ratio checks.
- The global pause switch is local and simple, reducing reliance on registry state during emergency pause.
- Oracle updates are guarded by `oracle-enabled` checks before resilient oracle calculation.
- No high or critical issue was found in this standalone static review.

