Contract: SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01
Source:
- Hiro API: https://api.hiro.so/v2/contracts/source/SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM/amm-pool-v2-01
- Local snapshot: /Users/adam/programming/other/10k/aibtc-agent/reports/amm-pool-v2-01.clar
This contract keeps very little local mutable state and delegates most pool bookkeeping to external contracts.
- Local constants: 27
define-constantentries. - Local mutable vars:
- L16:
(define-data-var paused bool true)
- L16:
- Externalized state:
- Pool balances, fee settings, oracle flags, thresholds, pool owner, and supply metadata are read from and written back to
.amm-registry-v2-01. - Liquidity token supply is minted and burned through
.token-amm-pool-v2-01. - Asset custody and reserve accounting live in
.amm-vault-v2-01.
- Pool balances, fee settings, oracle flags, thresholds, pool owner, and supply metadata are read from and written back to
Practical implication: this contract is mostly a router/controller around registry and vault state. Any security review has to treat cross-contract invariants as first-class.
| Function | Line | Authority | Preconditions / asserts | State mutations | External calls |
|---|---|---|---|---|---|
| pause | L198 | DAO or approved extension | none explicit | sets paused |
none |
| set-start-block | L202 | pool owner or DAO/extension; tx-sender bound | L205: (asserts! (or (is-eq tx-sender (get pool-owner pool)) (is-ok (is-dao-or-extension))) ERR-NOT-AUTHORIZED) | none | .amm-registry-v2-01 |
| set-end-block | L207 | pool owner or DAO/extension; tx-sender bound | L210: (asserts! (or (is-eq tx-sender (get pool-owner pool)) (is-ok (is-dao-or-extension))) ERR-NOT-AUTHORIZED) | none | .amm-registry-v2-01 |
| set-max-in-ratio | L212 | pool owner or DAO/extension; tx-sender bound | L215: (asserts! (or (is-eq tx-sender (get pool-owner pool)) (is-ok (is-dao-or-extension))) ERR-NOT-AUTHORIZED) | none | .amm-registry-v2-01 |
| set-max-out-ratio | L217 | pool owner or DAO/extension; tx-sender bound | L220: (asserts! (or (is-eq tx-sender (get pool-owner pool)) (is-ok (is-dao-or-extension))) ERR-NOT-AUTHORIZED) | none | .amm-registry-v2-01 |
| set-oracle-enabled | L222 | pool owner or DAO/extension; tx-sender bound | L225: (asserts! (or (is-eq tx-sender (get pool-owner pool)) (is-ok (is-dao-or-extension))) ERR-NOT-AUTHORIZED) | none | .amm-registry-v2-01 |
| set-oracle-average | L227 | pool owner or DAO/extension; tx-sender bound | L230: (asserts! (or (is-eq tx-sender (get pool-owner pool)) (is-ok (is-dao-or-extension))) ERR-NOT-AUTHORIZED) | none | .amm-registry-v2-01 |
| set-threshold-x | L232 | pool owner or DAO/extension; tx-sender bound | L235: (asserts! (or (is-eq tx-sender (get pool-owner pool)) (is-ok (is-dao-or-extension))) ERR-NOT-AUTHORIZED) | none | .amm-registry-v2-01 |
| set-threshold-y | L237 | pool owner or DAO/extension; tx-sender bound | L240: (asserts! (or (is-eq tx-sender (get pool-owner pool)) (is-ok (is-dao-or-extension))) ERR-NOT-AUTHORIZED) | none | .amm-registry-v2-01 |
| set-fee-rate-x | L242 | pool owner or DAO/extension; tx-sender bound | L245: (asserts! (or (is-eq tx-sender (get pool-owner pool)) (is-ok (is-dao-or-extension))) ERR-NOT-AUTHORIZED) | none | .amm-registry-v2-01 |
| set-fee-rate-y | L247 | pool owner or DAO/extension; tx-sender bound | L250: (asserts! (or (is-eq tx-sender (get pool-owner pool)) (is-ok (is-dao-or-extension))) ERR-NOT-AUTHORIZED) | none | .amm-registry-v2-01 |
| create-pool | L252 | pool owner or DAO/extension; tx-sender bound | L254: (asserts! (not (is-blocklisted-or-default tx-sender)) ERR-BLOCKLISTED) | creates pool in registry | .amm-registry-v2-01 |
| add-to-position | L257 | tx-sender bound | L270: (asserts! (not (is-paused)) ERR-PAUSED) L271: (asserts! (and (> dx u0) (> dy u0)) ERR-INVALID-LIQUIDITY) L272: (asserts! (>= (default-to u340282366920938463463374607431768211455 max-dy) dy) ERR-EXCEEDS-MAX-SLIPPAGE) |
updates pool record in registry, mints LP token | token-x-trait, token-y-trait, .amm-registry-v2-01, .token-amm-pool-v2-01 |
| reduce-position | L279 | tx-sender bound | L294: (asserts! (not (is-blocklisted-or-default tx-sender)) ERR-BLOCKLISTED) L295: (asserts! (not (is-paused)) ERR-PAUSED) L296: (asserts! (<= percent ONE_8) ERR-PERCENT-GREATER-THAN-ONE) |
updates pool record in registry, burns LP token, transfers assets out of vault | .token-amm-pool-v2-01, .amm-vault-v2-01, .amm-registry-v2-01 |
| swap-x-for-y | L302 | tx-sender bound | L319: (asserts! (not (is-blocklisted-or-default tx-sender)) ERR-BLOCKLISTED) L320: (asserts! (not (is-paused)) ERR-PAUSED) L322: (asserts! (> dx u0) ERR-INVALID-LIQUIDITY) L323: (asserts! (<= (div-down dy dx-net-fees) (get-price-internal balance-x balance-y factor)) ERR-INVALID-LIQUIDITY) L324: (asserts! (<= (default-to u0 min-dy) dy) ERR-EXCEEDS-MAX-SLIPPAGE) |
updates pool record in registry, moves fees into vault reserve, transfers assets out of vault | token-x-trait, .amm-vault-v2-01, .amm-registry-v2-01 |
| swap-y-for-x | L331 | tx-sender bound | L348: (asserts! (not (is-blocklisted-or-default tx-sender)) ERR-BLOCKLISTED) L349: (asserts! (not (is-paused)) ERR-PAUSED) L351: (asserts! (> dy u0) ERR-INVALID-LIQUIDITY) L352: (asserts! (>= (div-down dy-net-fees dx) (get-price-internal balance-x balance-y factor)) ERR-INVALID-LIQUIDITY) L353: (asserts! (<= (default-to u0 min-dx) dx) ERR-EXCEEDS-MAX-SLIPPAGE) |
updates pool record in registry, moves fees into vault reserve, transfers assets out of vault | token-y-trait, .amm-vault-v2-01, .amm-registry-v2-01 |
| swap-helper | L360 | open | none explicit | none | none |
| swap-helper-a | L364 | open | none explicit | none | none |
| swap-helper-b | L366 | open | none explicit | none | none |
| swap-helper-c | L371 | open | none explicit | none | none |
| Function | Line | Authority | Preconditions / asserts | State mutations | External calls |
|---|---|---|---|---|---|
| is-dao-or-extension | L17 | DAO or approved extension; tx-sender bound | L18: (ok (asserts! (or (is-eq tx-sender .executor-dao) (contract-call? .executor-dao is-extension contract-caller)) ERR-NOT-AUTHORIZED))) | none | .executor-dao |
| is-blocklisted-or-default | L19 | open | none explicit | none | .amm-registry-v2-01 |
| get-switch-threshold | L21 | open | none explicit | none | .amm-registry-v2-01 |
| get-pool-details-by-id | L23 | open | none explicit | none | .amm-registry-v2-01 |
| get-pool-details | L25 | open | none explicit | none | .amm-registry-v2-01 |
| get-pool-exists | L27 | open | none explicit | none | .amm-registry-v2-01 |
| is-paused | L29 | open | none explicit | none | none |
| get-balances | L31 | open | none explicit | none | none |
| get-start-block | L36 | open | none explicit | none | none |
| get-end-block | L38 | open | none explicit | none | none |
| get-max-in-ratio | L40 | open | none explicit | none | none |
| get-max-out-ratio | L42 | open | none explicit | none | none |
| check-pool-status | L44 | open | L48: (ok (asserts! (and (>= block-height (get start-block pool)) (<= block-height (get end-block pool))) ERR-NOT-AUTHORIZED)))) | none | none |
| get-oracle-enabled | L49 | open | none explicit | none | none |
| get-oracle-average | L51 | open | none explicit | none | none |
| get-oracle-resilient | L53 | open | L58: (asserts! (get oracle-enabled pool) ERR-ORACLE-NOT-ENABLED) | none | none |
| get-oracle-instant | L61 | open | L66: (asserts! (get oracle-enabled pool) ERR-ORACLE-NOT-ENABLED) | none | none |
| get-price | L70 | open | none explicit | none | none |
| get-threshold-x | L75 | open | none explicit | none | none |
| get-threshold-y | L77 | open | none explicit | none | none |
| get-fee-rebate | L79 | open | none explicit | none | none |
| get-fee-rate-x | L81 | open | none explicit | none | none |
| get-fee-rate-y | L83 | open | none explicit | none | none |
| get-pool-owner | L85 | pool owner or DAO/extension | none explicit | none | none |
| get-y-given-x | L87 | open | L95: (asserts! (< dx (mul-down (get balance-x pool) (get max-in-ratio pool))) ERR-MAX-IN-RATIO) L96: (asserts! (< dy (mul-down (get balance-y pool) (get max-out-ratio pool))) ERR-MAX-OUT-RATIO) |
none | none |
| get-x-given-y | L98 | open | L106: (asserts! (< dy (mul-down (get balance-y pool) (get max-in-ratio pool))) ERR-MAX-IN-RATIO) L107: (asserts! (< dx (mul-down (get balance-x pool) (get max-out-ratio pool))) ERR-MAX-OUT-RATIO) |
none | none |
| get-y-in-given-x-out | L109 | open | L117: (asserts! (< dy (mul-down (get balance-y pool) (get max-in-ratio pool))) ERR-MAX-IN-RATIO) L118: (asserts! (< dx (mul-down (get balance-x pool) (get max-out-ratio pool))) ERR-MAX-OUT-RATIO) |
none | none |
| get-x-in-given-y-out | L120 | open | L128: (asserts! (< dx (mul-down (get balance-x pool) (get max-in-ratio pool))) ERR-MAX-IN-RATIO) L129: (asserts! (< dy (mul-down (get balance-y pool) (get max-out-ratio pool))) ERR-MAX-OUT-RATIO) |
none | none |
| get-x-given-price | L131 | open | L135: (asserts! (< price (get-price-internal (get balance-x pool) (get balance-y pool) factor)) ERR-NO-LIQUIDITY) | none | none |
| get-y-given-price | L137 | open | L141: (asserts! (> price (get-price-internal (get balance-x pool) (get balance-y pool) factor)) ERR-NO-LIQUIDITY) | none | none |
| get-token-given-position | L143 | open | L148: (asserts! (and (> dx u0) (> dy u0)) ERR-NO-LIQUIDITY) | none | none |
| get-position-given-mint | L150 | open | L154: (asserts! (> (get total-supply pool) u0) ERR-NO-LIQUIDITY) | none | none |
| get-position-given-burn | L156 | open | L160: (asserts! (> (get total-supply pool) u0) ERR-NO-LIQUIDITY) | none | none |
| get-helper | L162 | open | none explicit | none | none |
| get-helper-a | L166 | open | none explicit | none | none |
| get-helper-b | L168 | open | none explicit | none | none |
| get-helper-c | L173 | open | none explicit | none | none |
| fee-helper | L178 | open | none explicit | none | none |
| fee-helper-a | L182 | open | none explicit | none | none |
| fee-helper-b | L184 | open | none explicit | none | none |
| fee-helper-c | L188 | open | none explicit | none | none |
| get-invariant | L192 | open | none explicit | none | none |
| get-max-ratio-limit | L196 | open | none explicit | none | .amm-registry-v2-01 |
The highest-risk public flows are the ones that move tokens or LP supply:
| Function | Token movements callers should protect | Recommended caller post-conditions |
|---|---|---|
create-pool |
transfers dx + derived dy from caller into vault; mints LP token |
sender FT decrease for both input tokens; LP token increase to caller |
add-to-position |
transfers dx + derived dy into vault; mints LP token |
sender FT decrease for both inputs; LP token increase |
reduce-position |
burns LP token; transfers token X/Y from vault to caller | LP token decrease; minimum X/Y receive post-conditions |
swap-x-for-y |
transfers token X from caller, transfers token Y from vault, books fees | token X decrease capped at input; token Y increase at or above min-dy |
swap-y-for-x |
transfers token Y from caller, transfers token X from vault, books fees | token Y decrease capped at input; token X increase at or above min-dx |
swap-helper* and swap-*-multi |
chained transfers through one or more pools | per-hop minimum output where available; aggregate receive floor on terminal asset |
| admin setters | no user asset movement, but registry writes alter future swap behavior | no token post-conditions; governance callers should audit target values out-of-band |
- Global pause: local
pausedflag gates liquidity and swap flows. - DAO / extension control:
pauserequiresis-dao-or-extension, which checks eithertx-sender == .executor-daoor extension approval in.executor-dao. - Pool-owner control: setter functions such as
set-start-block,set-end-block, fee setters, threshold setters, and oracle toggles allow either the pool owner or DAO/extension to mutate registry-backed configuration. - Oracle dependencies: oracle-enabled behavior relies on pool metadata in
.amm-registry-v2-01and the resilient-oracle helper path. - Privileged surfaces:
.amm-registry-v2-01for pool metadata creation and updates..amm-vault-v2-01for reserve and transfer operations..token-amm-pool-v2-01for LP mint/burn authority.
tx-senderis used throughout public liquidity and admin flows. That is likely intentional, but it couples authorization and transfer source semantics to the outermost caller and reduces composability for wrappers or smart-wallet patterns.as-contractis used for registry and vault writes in multiple admin and swap paths. That is a normal pattern here, but it makes cross-contract invariant review mandatory because the effective caller changes during those subcalls.- Arithmetic-heavy pricing helpers (
pow-down,pow-up, invariant math, quote helpers) are a critical precision surface; they deserve fuzzing beyond static review. - No obvious trait-conformance gap surfaced from the top-level contract text. The contract consistently uses the declared SIP-010 and pool traits.
| ID | Severity | Function | Line | Finding | Recommended fix |
|---|---|---|---|---|---|
| ALEX-1 | low | swap-x-for-y | L315 | Reserve updates clamp negative balances to zero instead of aborting, which can hide quote overshoot or invariant drift if a future math change produces dy > balance-y. |
Assert reserve sufficiency before state mutation and revert instead of saturating to zero. |
| ALEX-2 | low | swap-y-for-x | L343 | The mirror path uses the same saturating pattern for balance-x, so a bad quote or rounding overshoot would silently zero reserves instead of failing fast. |
Use an explicit asserts! (> balance-x dx) or equivalent invariant guard before pool state is committed. |
| ALEX-3 | informational | get-oracle-resilient | L60 | When the resilient oracle snapshot is unset, the contract silently falls back to the instantaneous pool price, reducing manipulation resistance exactly when the resilient feed is absent. | Surface an explicit uninitialized-oracle state or require the resilient snapshot to be set before oracle-enabled pools rely on the blended price. |
swap-x-for-yfloors reserve underflow to zero instead of reverting if the quote path ever overshoots available Y reserve.swap-y-for-xmirrors the same saturating-reserve behavior on X reserve updates.get-oracle-resilientsilently falls back to the instantaneous pool price when the resilient snapshot is unset, weakening oracle hardening.