ALEX amm-registry-v2-01

Pool registry and configuration contract for the ALEX DEX β€” stores pool metadata, fee rates, oracle settings, and blocklist

ContractSP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-registry-v2-01
ProtocolALEX β€” leading DEX on Stacks with weighted/stable pools
SourceVerified on-chain via Hiro API (/v2/contracts/source)
Clarity VersionPre-Clarity 4 (publish height 152429)
Lines of Code~220
Confidence🟒 HIGH β€” single registry contract, all functions straightforward; no external calls except DAO auth check
DateFebruary 25, 2026
0
Critical
0
High
2
Medium
2
Low
3
Info

Overview

amm-registry-v2-01 is the pool configuration registry for the ALEX DEX. It does not hold or transfer any tokens β€” it stores pool metadata (balances, fee rates, oracle settings, thresholds) in maps and provides getter/setter functions. All state-mutating functions are gated behind the ALEX DAO (executor-dao), meaning only approved governance proposals or extensions can modify pool configuration.

This is a companion contract to amm-pool-v2-01, which handles the actual swap logic and token transfers. The registry stores the pool state that the pool contract reads and updates.

Architecture

Trust Assumptions

Findings

MEDIUM

M-01: update-pool allows pool-id mismatch between maps

Location: update-pool function

Description: The update-pool function accepts a full pool-data struct (including pool-id) and writes it directly to pools-data-map. However, it does not validate that the pool-id in the supplied struct matches the pool-id originally assigned when the pool was created. This means the DAO could (accidentally or intentionally) set a pool-id in pools-data-map that doesn't match the reverse lookup in pools-id-map.

(define-public (update-pool (token-x principal) (token-y principal) (factor uint)
    (pool-data {
        pool-id: uint,      ;; <-- no validation against pools-id-map
        total-supply: uint,
        balance-x: uint,
        balance-y: uint,
        ...}))
    (begin
        (try! (is-dao-or-extension))
        (ok (map-set pools-data-map { token-x: token-x, token-y: token-y, factor: factor } pool-data))))

Impact: get-pool-details-by-id would return the original key, but reading the pool data via that key would show a different pool-id. Any off-chain or on-chain logic that cross-references pool-id across both maps could break or return incorrect data. Since the pool contract (amm-pool-v2-01) uses this registry for pool state, inconsistent pool-ids could confuse integrators.

Recommendation: Either remove pool-id from the update-pool input struct (derive it from the existing map entry), or add an assertion:

(asserts! (is-eq (get pool-id pool-data) (get pool-id existing-pool)) ERR-INVALID-POOL)
MEDIUM

M-02: No upper bound validation on fee rates

Location: set-fee-rate-x, set-fee-rate-y

Description: Fee rate setters accept any uint value with no maximum bound. Unlike set-oracle-average (which validates < ONE_8) and set-max-in-ratio/set-max-out-ratio (which validate against max-ratio-limit), fee rates can be set to values exceeding 100% (ONE_8 = u100000000).

(define-public (set-fee-rate-x (token-x principal) (token-y principal) (factor uint) (fee-rate-x uint))
    (let (
            (pool (try! (get-pool-details token-x token-y factor))))
        (try! (is-dao-or-extension))
        ;; No bounds check on fee-rate-x
        (ok (map-set pools-data-map ... (merge pool { fee-rate-x: fee-rate-x })))))

Impact: A malicious or buggy DAO proposal could set fee rates above 100%, which β€” depending on how the pool contract interprets them β€” could cause swaps to fail, drain more fees than the swap amount, or create arithmetic overflow/underflow conditions.

Recommendation: Add validation: (asserts! (<= fee-rate-x ONE_8) ERR-PERCENT-GREATER-THAN-ONE). The error constant ERR-PERCENT-GREATER-THAN-ONE already exists in the contract but is never used.

LOW

L-01: No ordering validation on start-block and end-block

Location: set-start-block, set-end-block

Description: The start and end block setters operate independently with no cross-validation. It's possible to set start-block > end-block, creating an invalid time window where the pool can never be active (or is always active, depending on how the pool contract interprets the range).

Impact: Low β€” requires a DAO proposal to trigger, and the pool contract may have its own validation. But the registry should enforce basic invariants at the source.

Recommendation: Add cross-validation: when setting start-block, assert new-start-block <= end-block; when setting end-block, assert new-end-block >= start-block.

LOW

L-02: No upper bound on fee-rebate

Location: set-fee-rebate

Description: Similar to M-02, fee-rebate has no bounds check. A rebate exceeding 100% could cause the pool contract to pay out more in rebates than collected in fees.

Recommendation: Add (asserts! (<= fee-rebate ONE_8) ERR-PERCENT-GREATER-THAN-ONE).

INFO

I-01: Pre-Clarity 4 contract

Description: This contract was deployed before Clarity 4 (epoch 3.3). While it doesn't use as-contract (no token operations), it cannot benefit from Clarity 4 safety features like as-contract? with explicit asset allowances or contract-hash? for on-chain code verification.

Recommendation: If the registry is ever redeployed, use Clarity 4 to benefit from the latest safety builtins.

INFO

I-02: Unused error constant ERR-PERCENT-GREATER-THAN-ONE

Description: The error constant ERR-PERCENT-GREATER-THAN-ONE (err u5000) is defined but never referenced in any validation. It appears intended for fee rate bounds checking that was never implemented.

Recommendation: Use this constant in fee rate and rebate validation (see M-02, L-02).

INFO

I-03: Pools cannot be deleted or deactivated

Description: Once created, a pool entry cannot be removed from the registry. The only way to effectively disable a pool is to set start-block and end-block to values that prevent trading. This is a common pattern in Clarity contracts (maps don't support deletion), but worth noting for operational awareness.

Recommendation: Consider adding an explicit active: bool field to pool data for cleaner pool lifecycle management, or document the start/end block approach as the canonical deactivation mechanism.

Positive Observations

Related Audits