Submission candidate for AIBTC bounty mpwj1ido1a0890ed463c.
- Contract:
SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 - Source:
https://api.hiro.so/v2/contracts/source/SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM/amm-pool-v2-01 - Source SHA-256:
3d9c4e01cad7a84da34435445e35d384e95a69e754a5b2916d5c61c42dfa4107 - Source lines reviewed: 622
- Review type: source-only static analysis
No high or critical findings were identified in this review. The findings below are low or informational and can be submitted publicly under the bounty rules.
I checked the visible public submission summaries before writing this report. The already-crowded themes are not repeated as primary findings here: add-to-position blocklist coverage, setter upper-bound checks for oracle averages and fee rates, reverse TWAP orientation, saturating swap reserve updates, unwrap-panic in math helpers, and multi-hop final-only slippage checks.
This report instead focuses on quote-helper accuracy and exact-output quote robustness, because those surfaces matter to agents, routers, and indexers that need to quote or verify ALEX AMM routes without causing bad user expectations.
| Store | Line | Type | Mutated by | Authority |
|---|---|---|---|---|
paused |
16 | bool |
pause |
DAO or DAO extension through is-dao-or-extension |
The contract stores no local pool map. Pool details, blocklist/default-account state, max ratio settings, start/end blocks, oracle settings, fee settings, and pool-owner data are delegated to .amm-registry-v2-01.
| Dependency | Used for | Mutating paths |
|---|---|---|
.amm-registry-v2-01 |
Pool records, blocklist/default-account checks, switch threshold, max ratio limits, fee/oracle/pool parameter setters | create-pool, add-to-position, reduce-position, swaps, owner/DAO setters |
.amm-vault-v2-01 |
Custody and reserve movement for pool tokens | add-to-position, reduce-position, swap-x-for-y, swap-y-for-x |
.token-amm-pool-v2-01 |
AMM LP share balance, mint, burn | add-to-position, reduce-position |
.executor-dao |
DAO extension authorization | pause, pool parameter setters |
| SIP-010 token traits | User token transfers into/out of the vault | Liquidity and swap entrypoints |
| Constant group | Lines | Purpose |
|---|---|---|
AMM/user errors ERR-NOT-AUTHORIZED through ERR-MAX-OUT-RATIO |
2-15 | Typed errors for authorization, pool validity, blocklist/default account, slippage, oracle, pause, ratio, and liquidity checks. |
| Fixed-point unit and pow error settings | 481-509 | 8-decimal math unit, relative error constant, exponent bounds, and mild exponent cap. |
| Exponent/log approximation tables and pow/log errors | 510-527 | Fixed-point exponent/log helper tables and error responses. |
| State | Mutations |
|---|---|
paused |
pause writes the local boolean. |
| Pool balances and parameters | Registry setters and update-pool calls mutate records in .amm-registry-v2-01; this contract computes the proposed merged pool records. |
| Vault balances/reserves | Token transfers to/from .amm-vault-v2-01, plus reserve additions for fees. |
| LP supply | LP shares are minted on add and burned on reduce through .token-amm-pool-v2-01. |
| Function | Lines | Authority | Preconditions / asserts | State mutations | External calls / transfers |
|---|---|---|---|---|---|
pause |
198-201 | DAO or DAO extension | try! is-dao-or-extension |
Writes paused. |
None. |
set-start-block |
202-206 | Pool owner by tx-sender, DAO, or DAO extension |
Existing pool; owner/DAO check. | Registry start block. | as-contract registry setter. |
set-end-block |
207-211 | Pool owner by tx-sender, DAO, or DAO extension |
Existing pool; owner/DAO check. | Registry end block. | as-contract registry setter. |
set-max-in-ratio |
212-216 | Pool owner by tx-sender, DAO, or DAO extension |
Existing pool; owner/DAO check. | Registry max-in ratio. | as-contract registry setter. |
set-max-out-ratio |
217-221 | Pool owner by tx-sender, DAO, or DAO extension |
Existing pool; owner/DAO check. | Registry max-out ratio. | as-contract registry setter. |
set-oracle-enabled |
222-226 | Pool owner by tx-sender, DAO, or DAO extension |
Existing pool; owner/DAO check. | Registry oracle-enabled flag. | as-contract registry setter. |
set-oracle-average |
227-231 | Pool owner by tx-sender, DAO, or DAO extension |
Existing pool; owner/DAO check. | Registry oracle average. | as-contract registry setter. |
set-threshold-x |
232-236 | Pool owner by tx-sender, DAO, or DAO extension |
Existing pool; owner/DAO check. | Registry threshold x. | as-contract registry setter. |
set-threshold-y |
237-241 | Pool owner by tx-sender, DAO, or DAO extension |
Existing pool; owner/DAO check. | Registry threshold y. | as-contract registry setter. |
set-fee-rate-x |
242-246 | Pool owner by tx-sender, DAO, or DAO extension |
Existing pool; owner/DAO check. | Registry x fee rate. | as-contract registry setter. |
set-fee-rate-y |
247-251 | Pool owner by tx-sender, DAO, or DAO extension |
Existing pool; owner/DAO check. | Registry y fee rate. | as-contract registry setter. |
create-pool |
252-256 | Open except blocklisted/default callers | Caller not blocklisted/default; registry create succeeds. | Creates registry pool, then delegates initial liquidity to add-to-position. |
Registry create, token transfers via delegated add. |
add-to-position |
257-278 | Open | Not paused; positive dx and derived dy; optional max-dy slippage cap. |
Registry pool balances and total supply; LP mint. | Transfers x and y from caller to vault; mints LP shares. |
reduce-position |
279-301 | Open except blocklisted/default callers | Caller not blocklisted/default; not paused; percent <= ONE_8. |
Registry pool balances and total supply; LP burn. | Vault transfers both tokens to caller; burns LP shares. |
swap-x-for-y |
302-330 | Open except blocklisted/default callers | Not blocklisted/default; not paused; pool active; positive input; price and min-dy checks. |
Registry pool balances, fee reserve, resilient oracle. | Transfers x into vault, y out of vault, and fee to reserve. |
swap-y-for-x |
331-359 | Open except blocklisted/default callers | Not blocklisted/default; not paused; pool active; positive input; price and min-dx checks. |
Registry pool balances, fee reserve, resilient oracle. | Transfers y into vault, x out of vault, and fee to reserve. |
swap-helper |
360-363 | Open | Selects direct or reverse pool by get-pool-exists; delegates to directional swap. |
Same as selected swap. | Same as selected swap. |
swap-helper-a |
364-365 | Open | First hop result is passed to second hop; final min-dz only. |
Same as nested swaps. | Two nested swaps. |
swap-helper-b |
366-370 | Open | Two-hop helper result is passed to third hop; final min-dw only. |
Same as nested swaps. | Three nested swaps. |
swap-helper-c |
371-375 | Open | Two two-hop helpers chained; final min-dv only. |
Same as nested swaps. | Four nested swaps. |
| Group | Functions | Lines | Notes |
|---|---|---|---|
| Auth/status wrappers | is-dao-or-extension, is-blocklisted-or-default, is-paused, check-pool-status |
17-20, 29-30, 44-48 | Authorization and status helpers. |
| Pool getters | get-pool-details-by-id, get-pool-details, get-pool-exists, balances/start/end/max ratio/oracle/threshold/fee/owner getters |
21-86 | Mostly thin registry wrappers. |
| Swap/liquidity quote helpers | get-y-given-x, get-x-given-y, exact-out helpers, price-target helpers, LP mint/burn quote helpers |
87-161 | Compute quotes from registry balances and math helpers. |
| Multi-hop quote helpers | get-helper, get-helper-a, get-helper-b, get-helper-c, fee-helper* |
162-191 | Public read-only route quote/fee helpers. |
| AMM/math internals | invariant, price, swap formula, LP formula, mul/div/pow/log helpers | 192-197, 376-622 | Private fixed-point math and approximation routines. |
Callers should attach post-conditions even when the entrypoint has a min-* parameter. The contract returns useful values, but token movement occurs through external token traits and the vault.
| Public function | Token movement expected | Suggested caller post-conditions |
|---|---|---|
create-pool |
Caller transfers dx x-token and derived/initial dy y-token to vault; caller receives LP shares. |
Cap both token transfers; require LP share receipt at or above expected minimum if supported by the wallet/post-condition tooling; verify pool owner and factor before signing. |
add-to-position |
Caller transfers dx x-token and derived dy y-token to vault; caller receives LP shares. |
Cap x at dx, cap y at max-dy, and require a minimum LP-share receipt or use a wrapper that enforces min-supply. |
reduce-position |
Caller burns LP shares and receives both underlying pool tokens. | Cap LP burned; require minimum x and y received. |
swap-x-for-y |
Caller transfers gross x input; vault returns y output if dy > 0; fee reserve receives unrebatched x fee. |
Cap gross x spent; require y received >= min-dy; constrain token contracts and vault principal. |
swap-y-for-x |
Caller transfers gross y input; vault returns x output if dx > 0; fee reserve receives unrebatched y fee. |
Cap gross y spent; require x received >= min-dx; constrain token contracts and vault principal. |
swap-helper |
Same as selected direct or reverse swap. | Treat min-dy as minimum final output in the requested pair orientation; cap input token and require output token receipt. |
swap-helper-a / b / c |
Sequential swaps through two, three, or four pools. Intermediate outputs are consumed as subsequent inputs. | Cap the first input token, require final output >= caller minimum, and use explicit route validation off-chain for intermediate pools/tokens because the public helper does not expose per-hop minimums. |
| Surface | Authority model | Notes |
|---|---|---|
| Pause switch | DAO or DAO extension | pause requires is-dao-or-extension. |
| Pool parameter setters | Pool owner by tx-sender, DAO, or DAO extension |
Setters check tx-sender == pool-owner or DAO extension status, then call registry setters as this contract. |
| User liquidity adds | Open | create-pool checks the blocklist/default-account helper; direct add-to-position does not repeat that check. This gap is already well covered by prior public submissions, so it is not a primary finding here. |
| User liquidity removes | Open except blocklisted/default callers | reduce-position checks blocklist/default-account status, pause, and percent. |
| User swaps | Open except blocklisted/default callers | Both directional swaps check blocklist/default-account status, pause, and pool start/end window. |
| Registry | External authority | Registry controls persisted pool records and blocklist/default-account answers. |
| Vault | External custody | Vault transfer helpers custody actual pool tokens and fee reserves. |
| LP token | External LP accounting | LP balance, mint, and burn are delegated to .token-amm-pool-v2-01. |
| Check | Result |
|---|---|
tx-sender vs contract-caller |
Setter owner checks use tx-sender == pool-owner and DAO extension checks use contract-caller through .executor-dao. This is already public in visible submissions, so this report treats it as duplicate-aware background, not a new primary finding. |
unwrap-panic / unwrap-err-panic in user-facing path |
reduce-position unwraps the LP balance response at line 286; pow-down and pow-up unwrap pow-fixed at lines 496 and 502. Existing submissions already highlight this class. |
Arithmetic overflow/underflow risk in * / + / - |
Exact-output quote internals for the factor >= switch-threshold branch subtract requested output from balances before the public max-out asserts can return typed errors. See ALEX-L02. |
as-contract usage / principal escalation |
Registry, vault, reserve, LP mint, and LP burn calls use as-contract. This is expected because the registry/vault/LP token treat the AMM contract as the authorized caller. |
| Trait conformance gaps | The contract accepts SIP-010 traits and delegates transfers through transfer-fixed; callers should still constrain token principals with post-conditions. |
| Quote helper accuracy | Read-only route quote helpers do not subtract fees, while fee helpers return separate nominal fee rates. See ALEX-L01. |
| ID | Severity | Function | Line | Finding | Recommended fix |
|---|---|---|---|---|---|
| ALEX-L01 | Low | get-helper, get-helper-a, get-helper-b, get-helper-c, fee-helper* |
162-191 | Read-only route quote helpers are fee-blind. get-helper* passes gross input amounts into get-y-given-x / get-x-given-y, while the real swap paths first subtract mul-up(input fee-rate) before calculating output. fee-helper* exists separately, but it returns nominal route fee-rate sums rather than the actual compounded net-input path. Frontends, agents, or off-chain verifiers that treat get-helper* as executable output quotes will overestimate received tokens. |
Add fee-aware quote helpers that mirror swap-x-for-y / swap-y-for-x net-input calculation at each hop, or rename/document get-helper* as gross pre-fee curve output and require integrators to combine it with a compounded fee model. |
| ALEX-L02 | Low | get-y-in-given-x-out, get-x-in-given-y-out, exact-out internals |
109-129, 412-432 | Exact-output quote helpers can abort before returning their typed max-out-ratio error on the factor >= switch-threshold branch. The public wrappers compute dy / dx in a let binding before the max-out asserts at lines 117-118 and 128-129, while the internal branch subtracts dx from balance-x or dy from balance-y directly at lines 416 and 432. A quote request at or above reserves can therefore hit an arithmetic underflow/runtime abort instead of returning ERR-MAX-OUT-RATIO. |
Guard requested output against reserves and max-out ratio before calling the internal math, or make the high-factor branch use the same saturating guard style used in the low-factor branch and then return a typed error. |
| ALEX-L03 | Low | add-to-position, get-token-given-position-internal |
257-278, 470-473 | Liquidity adds expose a y-token slippage cap but no first-class minimum LP-share output. For non-empty pools, minted shares are derived from dx / balance-x, while max-dy only caps the paired y deposit. If pool balances move between quote and execution, a caller can satisfy max-dy and still receive fewer LP shares than expected unless the wallet or wrapper attaches an LP-share post-condition. |
Add an optional min-supply parameter to add-to-position / create-pool, or document that callers must enforce minimum LP-share receipt through post-conditions or a wrapper. |
| ALEX-I01 | Informational | get-x-given-price, get-x-given-price-internal |
131-136, 444-456 | The x-needed-for-target-price quote path does not explicitly reject a zero target price before the internal division. The public wrapper only checks that price is below the current pool price, so price = 0 can enter line 447 and trigger division-by-zero behavior instead of a typed ERR-NO-LIQUIDITY / ERR-OUT-OF-BOUNDS style response. |
Add asserts! (> price u0) before calling get-x-given-price-internal, with a typed quote error. |
- No high or critical finding is claimed. Private ALEX disclosure was not triggered by this report.
- I intentionally do not submit the public high-severity reverse-TWAP orientation claim because it is already visible in prior public submissions and would require careful private disclosure handling if it were newly asserted.
- I intentionally do not center the already-crowded
add-to-positionblocklist gap, oracle-average bound, fee-rate bound, broadunwrap-panic, or multi-hop final-only slippage themes. - This report treats the registry, vault, LP token, DAO, and SIP-010 contracts as external dependencies and does not claim they are correct beyond the call interfaces visible from
amm-pool-v2-01.
Top findings for AIBTC message:
- Low:
get-helper*route quote helpers are fee-blind and can overquote executable swap output. - Low: exact-output quote helpers can runtime-abort before returning typed max-out errors on high-factor pools.
- Low: liquidity adds have a y-token cap but no first-class minimum LP-share output parameter.