conxian-clp

2026-01-01

Conxian Protocol — Concentrated Liquidity Pool

Conxian/Conxian · Commit: 0c5d76f · DeFi / AMM · February 27, 2026

1
Contract
197
Lines
13
Findings
4
Critical

Overview

A concentrated liquidity AMM pool contract inspired by Uniswap V3. It supports pool creation, position minting/burning, token swaps with fee collection, and protocol fee distribution via a revenue distributor contract.

This contract is a skeleton — not a functional AMM. It maintains accounting data structures but lacks the actual token transfer logic needed for a working pool. Every core operation (mint, burn, swap, collect) is critically broken. The "concentrated liquidity" mechanism (ticks, sqrt-price) is entirely decorative — no price math exists.

Findings Summary

IDSeverityTitle
C-01CRITICALSwap sends tokens without receiving input tokens
C-02CRITICALMint does not transfer tokens into the pool
C-03CRITICALBurn does not return tokens to the user
C-04CRITICALNo access control on set-pool-fee
H-01HIGHNo access control on create-pool or initialize
H-02HIGHcollect never transfers fees or resets accumulators
H-03HIGHFee growth per position is never updated
M-01MEDIUMTicks and sqrt-price are decorative — no price math
M-02MEDIUMSwap uses 1:1 exchange instead of AMM curve
M-03MEDIUMUses legacy as-contract instead of Clarity 4 as-contract?
L-01LOWadd-liquidity is a no-op
I-01INFOPool token principals stored but never validated
I-02INFONo events or print statements

Detailed Findings

CRITICAL C-01: Swap Sends Tokens Without Receiving Input Tokens

The swap function transfers amount-out to the caller via contract-call? but never transfers amount-in from the caller to the contract. Any user can call swap and receive tokens for free.

;; Sends tokens OUT but never receives tokens IN:
(as-contract
  (if zero-for-one
    (try! (contract-call? token1-trait transfer amount-out (as-contract tx-sender) recipient none))
    (try! (contract-call? token0-trait transfer amount-out (as-contract tx-sender) recipient none))
  )
)
;; Missing: inbound transfer of amount-in from tx-sender to the contract

Impact: Total loss of all tokens held by the contract. Anyone can drain the pool in a single transaction.

Fix: Add an inbound transfer of amount-in from tx-sender to the contract before the outbound transfer.

CRITICAL C-02: Mint Does Not Transfer Tokens Into the Pool

The mint function increases the position's liquidity and the pool's total liquidity counter, but never calls any token transfer to receive tokens from the minter.

(map-set positions position-key
  (merge current-pos { liquidity: (+ (get liquidity current-pos) amount) }))
(map-set pools pool-id
  (merge pool { liquidity: (+ (get liquidity pool) amount) }))
;; No token transfer from tx-sender to contract

Impact: Pool liquidity is phantom — numbers go up but no real tokens back them. The accounting is completely disconnected from actual token balances.

Fix: Accept token traits as parameters and transfer appropriate amounts of token0 and token1 based on tick range and current price.

CRITICAL C-03: Burn Does Not Return Tokens to the User

The burn function decreases position and pool liquidity counters but never transfers tokens back to the user.

(map-set pools pool-id
  (merge pool { liquidity: (- (get liquidity pool) amount) }))
;; No token transfer back to tx-sender

Impact: Users who somehow deposited tokens could never withdraw them — permanent loss of funds.

Fix: Calculate token amounts owed based on liquidity and tick range, then transfer from the contract to the caller.

CRITICAL C-04: No Access Control on set-pool-fee

Any user can change the fee of any pool. No authorization check exists.

(define-public (set-pool-fee (pool-id uint) (new-fee uint))
  (begin
    (let ((pool (unwrap! (map-get? pools pool-id) (err ERR_INSUFFICIENT_LIQUIDITY))))
      (map-set pools pool-id (merge pool { fee: new-fee }))
      (ok true)
    )
  )
)

Impact: An attacker can set the fee to u1000000 (100%), making amount-out = 0, or set fee to u0 to bypass fee collection entirely.

Fix: Add access control — restrict to the pool creator or a governance/admin principal.

HIGH H-01: No Access Control on create-pool or initialize

Anyone can create unlimited pools with arbitrary parameters including fake token principals, zero prices, or extreme fees. The initialize function is just a wrapper around create-pool with no additional guards.

Impact: Pool ID namespace pollution. Users could be tricked into interacting with malicious pools.

HIGH H-02: collect Never Transfers Fees or Resets Accumulators

The collect function returns fee growth values as a response tuple but never transfers tokens to the caller and never resets the values.

(define-public (collect (pool-id uint) (tick-lower int) (tick-upper int))
  (let (...)
    (ok {
      collected-0: (get fee-growth-inside-0 position),
      collected-1: (get fee-growth-inside-1 position)
    })
  )
)

Impact: LP fees are never distributable, even if fee tracking were implemented.

HIGH H-03: Fee Growth Per Position Is Never Updated

The swap function adds lp-fee to the pool's total liquidity counter instead of distributing it to position fee accumulators. fee-growth-inside-0 and fee-growth-inside-1 remain at u0 forever.

;; Fees incorrectly added to liquidity counter:
(map-set pools pool-id
  (merge pool { liquidity: (+ (get liquidity pool) lp-fee) }))

Impact: LPs never earn fees from swaps. The fee mechanism is completely non-functional.

MEDIUM M-01: Ticks and sqrt-price Are Decorative

The contract stores tick, tick-lower, tick-upper, and sqrt-price but never uses them in any calculation. Swaps don't update price. Mint/burn don't calculate token amounts based on tick ranges. The concentrated liquidity mechanism is entirely absent.

MEDIUM M-02: Swap Uses 1:1 Exchange Instead of AMM Curve

Output is calculated as amount-out = amount-in - total-fee — a naive 1:1 swap minus fees. A real concentrated liquidity pool requires output based on sqrt-price, liquidity depth, and the constant-product invariant.

(amount-out (- amount-in total-fee))

MEDIUM M-03: Uses Legacy as-contract

The contract uses pre-Clarity 4 as-contract which grants blanket asset access. Any token trait passed to swap or collect-protocol-fees could execute arbitrary logic under the contract's authority.

Fix: Migrate to Clarity 4 and use as-contract? with explicit with-ft/with-stx allowances. Implement a token allowlist.

LOW L-01: add-liquidity Is a No-Op

(define-public (add-liquidity (amount0 uint) (amount1 uint) (token0 principal) (token1 principal))
  (ok true)
)

Dead code. May mislead integrators.

INFO I-01: Pool Token Principals Not Validated Against Traits

A pool's token0/token1 principals are stored at creation but never checked against the actual trait implementations passed to swap. A pool created with token0=X could be swapped with a different token trait.

INFO I-02: No Events

The contract emits no print events, making off-chain indexing of pool creation, swaps, mints, and burns impossible.

Verdict

Do not deploy. This contract is a skeleton with no functional AMM logic. Every core operation is critically broken — swaps drain the pool for free, mint/burn move no tokens, fees are never distributed, and anyone can change pool parameters. The concentrated liquidity mechanism (ticks, sqrt-price) exists only as data structure fields with no actual price math.

The contract requires a complete rewrite. Study production Clarity AMM implementations (ALEX amm-pool-v2-01, Velar univ2-core, Bitflow stableswap) before attempting concentrated liquidity, which is significantly more complex than constant-product AMMs.