ALEX Fixed Weight Pool v1-01

Balancer-style weighted AMM pool — supports unequal token weights, TWAP oracle, multi-hop routing via wSTX, and LP token management

ContractSP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.fixed-weight-pool-v1-01
ProtocolALEX — leading DEX on Stacks
SourceVerified on-chain via Hiro API (/v2/contracts/source)
Clarity VersionPre-Clarity 4 (uses as-contract, not as-contract?)
Lines of Code1,320
Audit Date2026-02-25
Confidence🟡 MEDIUM — complex math library inlined, multi-contract system (vault, weighted-equation, reserve-pool)
0
Critical
2
High
3
Medium
3
Low
4
Info

Overview

fixed-weight-pool-v1-01 is ALEX's original Balancer-style AMM. Unlike equal-weight (50/50) pools, this contract allows arbitrary weight ratios (e.g. 80/20), enabling pools where one token dominates price exposure. The contract:

Architecture

Key dependencies:

Findings

HIGH

H-01: Balance underflow protection masks insolvency

Location: reduce-position, swap-wstx-for-y, swap-y-for-wstx

Description: Throughout the contract, balance updates use a pattern that silently floors to zero instead of reverting on underflow:

balance-x: (if (<= balance-x dx) u0 (- balance-x dx)),
balance-y: (if (<= balance-y dy) u0 (- balance-y dy))

Impact: If the pool's internal accounting ever drifts from actual vault holdings (due to rounding, external vault interactions, or bugs in weighted-equation), the pool will silently become insolvent — setting balances to zero rather than reverting. This masks the accounting error and allows subsequent LPs or swappers to absorb the loss. A pool with balance-x = 0 and balance-y = 0 but nonzero total-supply would strand all LP tokens.

Recommendation: Replace the underflow guard with a hard assertion: (asserts! (> balance-x dx) ERR-INSUFFICIENT-BALANCE). If the pool truly doesn't have enough tokens, the transaction should revert, not silently corrupt state.

HIGH

H-02: Fee-to-address can set arbitrary fee rates with no upper bound

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

(define-public (set-fee-rate-x (token-x principal) ... (fee-rate-x uint))
    (let ((pool ...))
        (asserts! (or (is-eq tx-sender (get fee-to-address pool))
                      (is-ok (check-is-owner))) ERR-NOT-AUTHORIZED)
        (map-set pools-data-map ... (merge pool { fee-rate-x: fee-rate-x }))
        (ok true)))

Impact: The fee-to-address (multisig/DAO) can set fee rates to any value including 100% (ONE_8) or higher. A fee rate of ONE_8 means dx-net-fees becomes zero (or the fee exceeds the input), effectively bricking the pool for swappers. There's also no cap on fee-rebate — it could exceed 100%, causing the pool to retain more fees than collected.

Recommendation: Add upper-bound validation: (asserts! (< fee-rate-x MAX-FEE-RATE) ERR-FEE-TOO-HIGH) where MAX-FEE-RATE is a reasonable cap (e.g., 10% = u10000000). Similarly cap fee-rebate to <= ONE_8.

MEDIUM

M-01: Oracle resilient price can be manipulated via large swaps

Location: swap-wstx-for-y, swap-y-for-wstx — oracle update

oracle-resilient: (if (get oracle-enabled pool)
    (try! (get-oracle-resilient .token-wstx token-y weight-x weight-y))
    u0)

Description: The oracle-resilient price is an EMA that blends the instant spot price (post-swap) with the previous resilient price. However, get-oracle-resilient is called after the pool balances have been updated in the current swap's pool-updated merge. Since the map is set after the oracle read, the oracle actually reads the pre-swap balances (the map hasn't been updated yet). This is actually safer than it appears — but the oracle-average weighting still means a single large swap shifts the resilient price by (1 - oracle-average) fraction of the price impact. With a low oracle-average (e.g., 0.05), a single swap moves the resilient price by 95% of the spot move.

Impact: If other contracts rely on get-oracle-resilient for lending or liquidation decisions, a flash-loan-style large swap can significantly shift the oracle in a single transaction. Stacks doesn't have flash loans natively, but multi-call transactions can approximate this.

Recommendation: Consider enforcing a minimum oracle-average (e.g., ≥ 50%) to limit single-block manipulation. Alternatively, use a block-height-gated TWAP that only updates once per block.

MEDIUM

M-02: Swap invariant check allows full pool drainage in edge cases

Location: swap-wstx-for-y (line ~485), swap-y-for-wstx (line ~530)

;; swap-wstx-for-y:
(asserts! (< (div-down dy dx-net-fees)
             (div-down (mul-down balance-y weight-x)
                       (mul-down balance-x weight-y)))
          ERR-INVALID-LIQUIDITY)

;; swap-y-for-wstx:
(asserts! (> (div-down dy-net-fees dx)
             (div-down (mul-down balance-y weight-x)
                       (mul-down balance-x weight-y)))
          ERR-INVALID-LIQUIDITY)

Description: These checks verify the effective swap price is "better" than the marginal price, which should always hold for constant-product math. However, they don't limit the size of the swap relative to pool liquidity. A swap that drains 99% of one side is allowed as long as the price check passes. Combined with the balance-floor-to-zero pattern (H-01), this could leave a pool in a degenerate state.

Impact: Very large swaps relative to pool size cause extreme slippage and can leave the pool nearly empty on one side, making subsequent swaps extremely expensive. This is standard AMM behavior but combined with H-01 creates additional risk.

Recommendation: Consider adding a maximum swap size relative to pool balance (e.g., no single swap can drain more than 50% of either token balance).

MEDIUM

M-03: Pool token trait not validated at pool creation

Location: create-pool

Description: The create-pool function stores the pool token principal but only validates token matching in add-to-position and reduce-position. If the contract owner creates a pool with a malicious pool token that has unrestricted mint-fixed, anyone with that token contract's mint authority could mint LP tokens without depositing, then call reduce-position to drain pool funds.

Impact: Since pool creation is owner-restricted, this is a trust assumption on the owner. However, if ownership transfers to a DAO or multisig, the risk surface expands.

Recommendation: Document this trust assumption clearly. Consider adding a validation that the pool token's owner/minter is set to the fixed-weight-pool contract itself at creation time.

LOW

L-01: reduce-position uses unwrap-panic for balance query

Location: reduce-position

(total-shares (unwrap-panic (contract-call? pool-token-trait get-balance-fixed tx-sender)))

Description: If the pool token contract's get-balance-fixed returns an error (e.g., the trait implementation is buggy or the contract is upgraded), this will abort the entire transaction with a runtime panic rather than returning a graceful error.

Recommendation: Use (unwrap! ... ERR-TRANSFER-FAILED) for graceful error propagation.

LOW

L-02: Fee calculation can zero out swap input

Location: swap-wstx-for-y, swap-y-for-wstx

(fee (mul-up dx (get fee-rate-x pool)))
(dx-net-fees (if (<= dx fee) u0 (- dx fee)))

Description: If fee ≥ dx (possible with high fee rates or small swaps due to mul-up rounding), dx-net-fees becomes zero. A zero input to the weighted equation would produce zero output, and the user loses their entire input as fees.

Recommendation: Add (asserts! (> dx-net-fees u0) ERR-INVALID-LIQUIDITY) after the fee calculation.

LOW

L-03: No re-entrancy concern but as-contract gives blanket asset access

Location: Multiple — all as-contract calls

Description: The contract uses pre-Clarity 4 as-contract which gives unrestricted asset access within the expression scope. While Clarity's execution model prevents re-entrancy (no callbacks during contract-call?), the blanket access means any future code changes or upgrades within the as-contract block could accidentally authorize unintended asset transfers.

Recommendation: When migrating to Clarity 4, replace as-contract with as-contract? using explicit with-ft/with-stx allowances for each token being transferred.

INFO

I-01: Inlined math library duplicates code across ALEX contracts

Description: The entire Balancer-style log/exp/pow math library (~320 lines) is inlined in this contract. The same code appears in amm-swap-pool-v1-1 and likely other ALEX pool contracts. This increases deployment cost and makes bug fixes require redeploying every pool contract.

Recommendation: The math is already delegated for some functions via weighted-equation-v1-01. Consider moving all math to a shared library contract.

INFO

I-02: Multi-hop routing adds hidden fee compounding

Location: swap-x-for-y, swap-y-for-x

Description: When neither token is wSTX, swaps route through two pools (token-x → wSTX → token-y). Each hop applies its own fee. Users may not realize they're paying fees twice. The intermediate swap in swap-x-for-y passes none for min-dy, meaning the intermediate hop has no slippage protection.

Recommendation: Document the double-fee behavior. Consider adding end-to-end slippage protection that covers both hops (the outer min-dy partially addresses this but only on the final output).

INFO

I-03: Pool count hard-capped at 500

Location: create-pool

(var-set pools-list (unwrap! (as-max-len? (append (var-get pools-list) pool-id) u500) ERR-TOO-MANY-POOLS))

Description: The pools-list is limited to 500 entries. Once reached, no new pools can be created. This is a deployment constraint, not a bug, but worth noting for long-term scalability.

INFO

I-04: Owner set to executor-dao at deployment

Location: Last line of contract

(set-contract-owner .executor-dao)

Description: The contract owner is initialized to .executor-dao at deploy time. This is the correct pattern — governance via DAO rather than an EOA. The set-contract-owner function allows ownership transfer, which is appropriate for DAO upgrades.

Positive Observations

Summary

The ALEX Fixed Weight Pool is a mature, deployed contract implementing Balancer-style weighted AMM mechanics. The core swap and liquidity logic is sound, delegating complex math to a dedicated equation contract. The main concerns are operational: unbounded fee rates (H-02) could brick pools if governance is compromised, and the silent underflow protection (H-01) could mask insolvency rather than failing safely. The oracle implementation (M-01) is a standard EMA design but could be manipulated with low smoothing factors. As a pre-Clarity 4 contract, it uses blanket as-contract — migration to as-contract? with explicit allowances would improve safety.

Overall, this is a well-structured DeFi contract with appropriate access controls and slippage protections. The findings are primarily defense-in-depth improvements rather than exploitable vulnerabilities in the current deployment context.