Skip to content

Instantly share code, notes, and snippets.

@chedger
Created June 8, 2026 21:05
Show Gist options
  • Select an option

  • Save chedger/f5cd355c203c0fcbc10e3ea3c26037e3 to your computer and use it in GitHub Desktop.

Select an option

Save chedger/f5cd355c203c0fcbc10e3ea3c26037e3 to your computer and use it in GitHub Desktop.
AIBTC ALEX AMM pool v2 static analysis for bounty mpwj1ido1a0890ed463c

ALEX AMM Pool v2 Static Analysis

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

Summary

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.

1. State Model

Local Storage

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.

External State Dependencies

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

Constants

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 Mutation Summary

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.

2. Function Inventory

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.

Read-Only Inventory

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.

3. Post-Condition Coverage Matrix

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.

4. Authority / Access-Control Matrix

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.

5. Clarity Best-Practice Review

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.

6. Findings Table

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.

Non-Findings And Duplicate-Aware Notes

  • 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-position blocklist gap, oracle-average bound, fee-rate bound, broad unwrap-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.

Submission Summary

Top findings for AIBTC message:

  1. Low: get-helper* route quote helpers are fee-blind and can overquote executable swap output.
  2. Low: exact-output quote helpers can runtime-abort before returning typed max-out errors on high-factor pools.
  3. Low: liquidity adds have a y-token cap but no first-class minimum LP-share output parameter.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment