Contract: SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01
Audit date: 2026-06-08
Scope: amm-pool-v2-01, with directly relevant authorization and mutation
paths in amm-registry-v2-01 and amm-vault-v2-01.
The AMM pool contract is a routing and calculation layer over a separate
registry, vault, and LP-token contract. It retains only the global pause flag.
The most important issue is an authorization pattern repeated across every
pool-owner setter: it trusts tx-sender rather than requiring the immediate
contract-caller to be the pool owner. A malicious contract called by a pool
owner can therefore exercise the owner's configuration authority.
No high or critical issue was identified. The review found one medium, two low, and two informational findings.
| Severity | Count |
|---|---|
| Critical | 0 |
| High | 0 |
| Medium | 1 |
| Low | 2 |
| Informational | 2 |
| Store | Line | Initial value | Writers | Authority |
|---|---|---|---|---|
paused data-var |
16 | true |
pause |
DAO or registered DAO extension |
The contract declares no local data maps. Pool balances, supply, ownership,
fees, oracle configuration, block windows, thresholds, ratios, and blocklist
state live in amm-registry-v2-01. Token custody and reserves live in
amm-vault-v2-01. LP balances live in token-amm-pool-v2-01.
| Lines | Constants | Purpose |
|---|---|---|
| 2-15 | ERR-* u1000/u2000-u2012 |
Authorization, pool, liquidity, slippage, oracle, pause, and ratio errors |
| 481-482 | ONE_8, MAX_POW_RELATIVE_ERROR |
Fixed-point scale and power-error bound |
| 506-509 | exponent bounds | Fixed-point exponent safety limits |
| 510-522 | precomputed logarithm factors | Fixed-point logarithm approximation |
| 523-527 | math errors | Fixed-point bounds and exponent errors |
| External contract | State affected | Called by |
|---|---|---|
amm-registry-v2-01 |
pools, balances, supply, owners, fee/oracle/ratio settings, reserves metadata, blocklist | All setters, create, add/reduce liquidity, swaps |
amm-vault-v2-01 |
token custody and reserves | reduce liquidity and swaps |
token-amm-pool-v2-01 |
LP token supply and balances | add/reduce liquidity |
| Caller-supplied SIP-010 traits | User token balances | add liquidity and swaps |
All read-only functions are open. They do not mutate state.
| Group | Functions | Behavior / external calls |
|---|---|---|
| Authorization and policy | is-dao-or-extension, is-blocklisted-or-default, check-pool-status |
Reads DAO extension status, registry blocklist, and pool block window |
| Pool/config getters | get-pool-details-by-id, get-pool-details, get-pool-exists, is-paused, get-balances, get-start-block, get-end-block, get-max-in-ratio, get-max-out-ratio, get-oracle-enabled, get-oracle-average, get-threshold-x, get-threshold-y, get-fee-rebate, get-fee-rate-x, get-fee-rate-y, get-pool-owner, get-max-ratio-limit |
Reads local pause or registry state |
| Oracle/price | get-oracle-resilient, get-oracle-instant, get-price |
Calculates prices from registry balances/config |
| Swap quotes | get-y-given-x, get-x-given-y, get-y-in-given-x-out, get-x-in-given-y-out, get-x-given-price, get-y-given-price |
Fixed-point quote calculations with ratio limits |
| Liquidity quotes | get-token-given-position, get-position-given-mint, get-position-given-burn |
Calculates required tokens or LP shares |
| Route helpers | get-helper, get-helper-a, get-helper-b, get-helper-c, fee-helper, fee-helper-a, fee-helper-b, fee-helper-c |
Selects pool direction and composes multi-hop quotes/fees |
| Math | get-invariant |
Calculates the selected AMM invariant |
| Function(s) | Lines | Authority / preconditions | Mutations / transfers |
|---|---|---|---|
pause |
198-201 | DAO or DAO extension | Writes local paused |
set-start-block, set-end-block |
202-211 | Pool owner by tx-sender, or DAO/extension |
Registry block window |
set-max-in-ratio, set-max-out-ratio |
212-221 | Pool owner by tx-sender, or DAO/extension; registry caps ratios |
Registry ratio limits |
set-oracle-enabled, set-oracle-average |
222-231 | Pool owner by tx-sender, or DAO/extension; registry validates average |
Registry oracle config |
set-threshold-x, set-threshold-y |
232-241 | Pool owner by tx-sender, or DAO/extension |
Registry thresholds |
set-fee-rate-x, set-fee-rate-y |
242-251 | Pool owner by tx-sender, or DAO/extension |
Registry directional fees |
create-pool |
252-256 | Open; sender not blocklisted; add-liquidity path must be unpaused | Creates registry pool, transfers initial tokens, mints LP |
add-to-position |
257-278 | Open; unpaused; positive liquidity; max Y bound | Transfers X/Y to vault, updates registry, mints LP |
reduce-position |
279-301 | Open; sender not blocklisted; unpaused; percent <= 1e8 | Vault sends X/Y, registry updates, LP burns |
swap-x-for-y, swap-y-for-x |
302-359 | Open; sender not blocklisted; unpaused; pool active; positive input; optional minimum | User input to vault, vault output to user, reserve and registry updates |
swap-helper, swap-helper-a/b/c |
360-375 | Same checks as selected nested swaps | Executes one to four swaps; only final hop receives caller minimum |
| Public function | Token movement | Recommended caller post-conditions |
|---|---|---|
Setters and pause |
None | Bind exact contract/function and arguments; pool owners should reject unexpected intermediary calls |
create-pool |
X and Y from sender to vault; LP tokens to sender | Cap X/Y sent; require expected LP token minimum |
add-to-position |
X and calculated Y from sender to vault; LP tokens to sender | Cap X and Y sent; require LP token minimum; do not omit a meaningful max-dy |
reduce-position |
LP tokens burned; X/Y from vault to sender | Cap LP burn; require minimum X and minimum Y received |
swap-x-for-y |
X from sender to vault; Y from vault to sender | Cap X sent and require minimum Y received |
swap-y-for-x |
Y from sender to vault; X from vault to sender | Cap Y sent and require minimum X received |
| Multi-hop helpers | Input token from sender; intermediate vault movements; final output to sender | Cap initial input and require final output token minimum; bind all token principals and factors |
The optional minimum-output arguments default to zero, so transaction post-conditions are an important independent protection when callers omit them.
| Capability | Authorized principal | Check |
|---|---|---|
| Pause/unpause all pool operations | DAO or DAO extension | is-dao-or-extension |
| Change a pool's block window, ratios, oracle settings, thresholds, or fees | Pool owner by tx-sender, DAO, or DAO extension |
Repeated setter checks at lines 205-250 |
| Create pool | Any non-blocklisted transaction sender | Line 254 plus registry uniqueness |
| Add liquidity | Any transaction sender, including blocklisted senders | No blocklist check in add-to-position |
| Remove liquidity | Any non-blocklisted LP holder | Lines 286-299 |
| Swap | Any non-blocklisted sender while unpaused and pool active | Lines 319-328 / 348-357 |
| Transfer from vault | Approved DAO extension calling vault | Vault authorization |
| Update registry pool accounting | Approved DAO extension calling registry | Registry authorization |
The pool contract is expected to be an approved DAO extension, which is why it
wraps registry/vault calls in as-contract.
| Check | Result |
|---|---|
tx-sender vs contract-caller |
Pool-owner setters rely on tx-sender, exposing owner authority to intermediary-contract phishing; see ALEX-01 |
| Panic unwraps | reduce-position uses unwrap-panic on the LP balance read at line 286; see ALEX-04 |
| Arithmetic overflow / underflow | Dedicated fixed-point bounds exist. No exploitable arithmetic failure was proven. Registry permits arbitrary fee rates, creating a privileged configuration DoS risk; see ALEX-03 |
as-contract / principal escalation |
Used extensively and intentionally to make authorized registry/vault/LP calls. Caller-supplied traits remain constrained by registry pool identity and vault approved-token policy |
| Trait conformance | Caller-supplied tokens must implement the local SIP-010 trait. Registry and vault authorization provide additional containment |
| Slippage | Add liquidity has optional max Y; swaps have optional minimum output. Omitted values remove caller-level economic protection |
| Blocklist consistency | Create, remove, and swap paths enforce blocklist; direct add-liquidity does not |
| ID | Severity | Function | Line | Finding | Recommended fix |
|---|---|---|---|---|---|
| ALEX-01 | Medium | Ten pool-owner setters | 202-251 | Pool-owner authority is phishable through tx-sender. A malicious intermediary contract called by a pool owner retains that owner as tx-sender and can call any setter. It can change block windows, directional fees, ratios, thresholds, and oracle configuration. Registry fee setters are not capped, so the resulting configuration can disable or materially distort a pool. |
Require (is-eq contract-caller pool-owner) for the owner branch. If direct wallet calls must remain supported, require both tx-sender and contract-caller to equal the owner. Keep DAO-extension authorization as a separate explicit branch. |
| ALEX-02 | Low | add-to-position |
257-278 | Blocklisted principals can still add liquidity and receive LP tokens. create-pool, reduce-position, and both direct swap paths check is-blocklisted-or-default; add-to-position does not. This leaves a direct policy bypass and permits a blocked address to increase protocol exposure even though it cannot exit through this contract while blocked. |
Apply the same blocklist assertion at the start of add-to-position, or document why blocked liquidity additions are intentionally allowed. |
| ALEX-03 | Low | set-fee-rate-x/y and registry setters |
242-251; registry 181-190 | Directional fee rates have no upper bound. A pool owner or DAO can set fee rates at or above ONE_8, making net input zero and causing swaps in that direction to fail or behave unexpectedly. This amplifies ALEX-01 and makes accidental misconfiguration a pool availability risk. |
Enforce fee-rate-x < ONE_8 and fee-rate-y < ONE_8 in the registry, ideally with a stricter governance maximum. |
| ALEX-04 | Informational | reduce-position |
286 | A user-facing path uses unwrap-panic. Any error from the LP balance read aborts without the contract's structured error vocabulary, making integration failures harder to diagnose. Funds remain atomic and no loss was identified. |
Replace with unwrap! and a dedicated error code. |
| ALEX-05 | Informational | Swap and liquidity entrypoints | 257, 302, 331, 360-375 | Economic bounds are optional and default to permissive values. none means unlimited Y on add-liquidity or zero minimum output on swaps; helpers intentionally protect only the final hop. Integrators that omit bounds are exposed to price movement and sandwiching. |
Offer strict wrapper entrypoints requiring explicit bounds, and prominently document transaction post-conditions for all integrations. |
In Clarity, tx-sender remains the original transaction initiator across
contract calls, while contract-caller identifies the immediate caller. If a
pool-owner wallet calls an untrusted contract, that contract can call
set-end-block, set-fee-rate-x/y, or another setter while the AMM still sees
the owner as tx-sender. The subsequent registry mutation executes through
as-contract, so the registry correctly trusts the AMM extension and applies
the attacker's chosen value.
The issue requires the pool owner to call an attacker-controlled contract in the same transaction. It does not grant arbitrary callers owner authority, so it is rated medium rather than high.
The omission is specific and observable: blocklist checks exist at lines 254,
294, 319, and 348, but not in add-to-position. A blocked address can call the
function directly, transfer assets into the vault, and receive LP shares. Its
ability to withdraw remains blocked by reduce-position, but the inconsistent
policy can undermine compliance expectations and complicate remediation.
- Pool accounting and custody are separated into registry and vault contracts.
- Vault transfers require approved-token and DAO-extension checks.
- Swap paths enforce pause state, pool block windows, positive inputs, max in/out ratios, and optional minimum outputs.
- Registry caps max input/output ratios and oracle average.
- Every state-changing multi-contract workflow is atomic on error.
- Fixed-point math includes exponent and product bounds.
- No high or critical issue was found, so no private responsible-disclosure action was required before publishing this report.
- Have a pool-owner wallet call a mock malicious contract that invokes every
setter; verify current behavior succeeds, then verify the proposed
contract-callerfix rejects it. - Blocklist an address and prove direct
add-to-positioncurrently succeeds while create, reduce, and swap reject it. - Set each directional fee to
ONE_8and above; document and test the desired governance bound. - Force LP balance-read errors and verify
reduce-positionreturns a structured error after replacingunwrap-panic. - Property-test swap and liquidity post-conditions across all helper routes.