Skip to content

Instantly share code, notes, and snippets.

@sato820
Created June 7, 2026 01:03
Show Gist options
  • Select an option

  • Save sato820/1c9b4fed9bbc34747356a5dc3604454b to your computer and use it in GitHub Desktop.

Select an option

Save sato820/1c9b4fed9bbc34747356a5dc3604454b to your computer and use it in GitHub Desktop.
Static analysis report: ALEX AMM Pool v2 (AIBTC bounty mpwj1ido1a0890ed463c)

Static Analysis Report: ALEX AMM Pool v2

Target bounty: mpwj1ido1a0890ed463c

Contract: SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01

Source reviewed: https://api.hiro.so/v2/contracts/source/SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM/amm-pool-v2-01

Review type: static source review only. I did not execute mainnet swaps.

Responsible disclosure note: I found no high or critical severity issue in this pass. No private disclosure was required before this public report.

1. State Model

Local router/pool state:

Store Type Initial Mutated by Notes
paused bool data-var true pause Gates add/reduce/swap flows, but not read-only quote helpers

Registry-backed pool state is read and updated through .amm-registry-v2-01. The reviewed contract treats the registry as canonical for:

  • pool existence and pool-id
  • balance-x, balance-y
  • total-supply
  • start-block, end-block
  • max-in-ratio, max-out-ratio
  • oracle-enabled, oracle-average, oracle-resilient
  • threshold-x, threshold-y
  • fee-rebate, fee-rate-x, fee-rate-y
  • pool-owner

Asset state is held outside this contract:

  • SIP-010 token balances are moved through user-supplied token traits.
  • Pool reserves are held in .amm-vault-v2-01.
  • LP token balances are tracked by .token-amm-pool-v2-01.

Important constants:

Name Value / role
ONE_8 u100000000, fixed-point scale
MAX_POW_RELATIVE_ERROR u4, exponentiation rounding guard
ERR-* u1000..u2012 pool/control/swap errors
ERR-* u5009..u5013 fixed-point math bounds errors
MAX_NATURAL_EXPONENT, MIN_NATURAL_EXPONENT, MILD_EXPONENT_BOUND exponent/log bounds

2. Function Inventory

Read-only functions:

Group Functions Notes
Authority/status is-dao-or-extension, is-blocklisted-or-default, is-paused, check-pool-status is-dao-or-extension authorizes DAO or extension callers
Pool metadata get-pool-details*, get-pool-exists, get-balances, get-start-block, get-end-block, ratio/fee/oracle/threshold getters Registry passthroughs
Quote/oracle get-oracle-resilient, get-oracle-instant, get-price, get-y-given-x, get-x-given-y, exact-out helpers, price-target helpers Uses registry balances and fixed-point math
LP math get-token-given-position, get-position-given-mint, get-position-given-burn Converts between deposits/burns and LP supply
Multi-hop helpers get-helper, get-helper-a, get-helper-b, get-helper-c, fee-helper* Quote-only path helpers
Math get-invariant plus private fixed-point log/exp/pow helpers No storage mutation

Public functions:

Function Authority Preconditions / asserts State mutations / external calls
pause DAO or registered DAO extension is-dao-or-extension var-set paused
set-start-block, set-end-block, set-max-in-ratio, set-max-out-ratio, set-oracle-enabled, set-oracle-average, set-threshold-x, set-threshold-y, set-fee-rate-x, set-fee-rate-y pool owner or DAO/extension pool exists; caller authorized as-contract registry updates
create-pool any non-blocklisted caller blocklist check registry pool creation, then initial add-to-position
add-to-position open not paused; positive liquidity; dy <= max-dy transfers both tokens to vault, registry update, LP mint
reduce-position open not blocklisted; not paused; percent <= ONE_8 vault transfers both tokens, registry update, LP burn
swap-x-for-y open not blocklisted; not paused; pool active; dx > 0; output >= optional min transfers X to vault, transfers Y out, reserve/registry update
swap-y-for-x open not blocklisted; not paused; pool active; dy > 0; output >= optional min transfers Y to vault, transfers X out, reserve/registry update
swap-helper, swap-helper-a/b/c open delegated to swap functions chained swaps; only final helper call receives the optional min

3. Post-condition Coverage Matrix

Public function Token movement surface Suggested postconditions
create-pool User deposits initial X and Y; LP minted to caller At-most X spend dx; at-most Y spend dy; at-least/expected LP mint if supported by the LP token contract
add-to-position User deposits X plus computed Y; LP minted At-most X spend dx; at-most Y spend supplied max-dy; at-least LP mint based on quote
reduce-position LP burned; X and Y returned At-most/exact LP burn; at-least X and Y receives from vault
swap-x-for-y X in, Y out At-most X spend dx; at-least Y receive min-dy; bind token principals
swap-y-for-x Y in, X out At-most Y spend dy; at-least X receive min-dx; bind token principals
swap-helper-a/b/c Multiple swaps in sequence Final output minimum is not enough for user intent in all cases; attach postconditions for total initial input and final output, and prefer explicit single-hop minimums where available
Setter functions No user token movement No token postconditions needed; governance monitoring should track registry writes

4. Authority / Access-control Matrix

Surface Authority model Notes
Pause switch DAO or DAO extension Starts paused; swaps/liquidity require unpaused
Pool scheduling/ratios/oracle/fees/thresholds Pool owner or DAO/extension Per-pool owner is powerful and can alter fee, oracle, threshold, and max ratio behavior
Pool creation Any non-blocklisted caller Registry determines uniqueness/validity
Add liquidity Any caller while unpaused See finding ALEX-M01: blocklist is not checked here
Reduce liquidity Any non-blocklisted caller while unpaused Uses caller LP balance
Swaps Any non-blocklisted caller while unpaused and pool active Optional min values default to zero
Vault / registry / LP token calls as-contract Intended principal escalation to mutate protocol-owned registry/vault/LP state

5. Clarity Best-practice Review

  • tx-sender: used for user identity, blocklist checks, pool-owner checks, and transfer sender. This is expected for direct user calls, but integrators should consider proxy/contract-caller behavior.
  • contract-caller: used in DAO extension authorization.
  • as-contract: used for registry, vault, and LP token operations. This is intentional but should remain tightly scoped.
  • unwrap-panic: present in reduce-position for LP balance and in pow-down/pow-up for fixed-point math. The LP balance case is user-facing and could be converted to a normal error.
  • Arithmetic: extensive fixed-point math uses guarded helpers, but some public swap paths compute price-ratio divisions before proving the output amount is non-zero.
  • Trait conformance: token traits are SIP-010 typed via .trait-sip-010.sip-010-trait.

6. Findings Table

ID Severity Function Line Finding Recommended fix
ALEX-M01 Medium add-to-position 257-278 Add-liquidity does not check is-blocklisted-or-default, while create-pool, reduce-position, and both direct swap functions do. A blocklisted address cannot create, reduce, or swap, but can still add liquidity to an existing pool when the protocol is unpaused. Add the same blocklist assert used in reduce-position and swaps before accepting liquidity.
ALEX-M02 Medium set-oracle-average, get-oracle-resilient 227-231, 53-60 set-oracle-average does not enforce new-oracle-average <= ONE_8, even though ERR-ORACLE-AVERAGE-BIGGER-THAN-ONE exists. If a pool owner/DAO sets a larger value, get-oracle-resilient can underflow at (- ONE_8 oracle-average), disabling resilient oracle reads for that pool. Add asserts! (<= new-oracle-average ONE_8) ERR-ORACLE-AVERAGE-BIGGER-THAN-ONE in the setter.
ALEX-L01 Low swap-y-for-x 341, 352 If a very small positive dy-net-fees rounds to dx = 0, the price-ratio guard computes (div-down dy-net-fees dx). With dy-net-fees > 0 and dx = 0, this can panic/abort before returning a domain-specific liquidity error. Assert dx > u0 before the division and return ERR-INVALID-LIQUIDITY or ERR-EXCEEDS-MAX-SLIPPAGE.
ALEX-L02 Low swap-x-for-y 312, 323-326 The X->Y path can accept a positive dx with dy = 0 when the caller omits min-dy or passes zero. The function will transfer X, skip the zero Y transfer, update reserves, and return {dy: u0}. This is caller-protectable with min-dy, but the default behavior is sharp. Consider asserting dy > u0, or document that callers must always pass a non-zero minimum output.
ALEX-L03 Low swap-helper-a/b/c 364-375 Multi-hop public helpers pass none as the intermediate minimum and only apply the optional min to the final hop. A final min can still protect final output, but callers have no per-hop slippage controls through these helpers. Add helper variants that accept per-hop minimums, or document these helpers as convenience functions requiring strong transaction postconditions.
ALEX-I01 Informational reduce-position, pow-down, pow-up 286, 496, 502 unwrap-panic is used in user-reachable paths. The LP balance unwrap in reduce-position is the most visible case; if the LP token read unexpectedly errors, callers receive a panic rather than a protocol error. Replace user-facing unwrap-panic with unwrap! and a dedicated error code.

Overall assessment: the contract has clear registry/vault separation and many explicit ratio/slippage checks, but the admin parameter setters should consistently enforce fixed-point bounds, and swap paths should guard zero-output/zero-denominator edge cases before price-ratio divisions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment