# Review: Zest CLMM venue in `swap-router-stx-usdcx-jing-v6` Target: `Rapha-btc/jing-contracts-v3` @ `9dab9bd`, `contracts/stx-usdcx/swap-router-stx-usdcx-jing-v6.clar` (sha256 `d974ea913ef9e6bfd575af55125ca71b59310dd1b93c568a138ebf44e672e502`). Diff base: `swap-router-stx-usdcx-jing-v5-5.clar` (same folder). Differential review: only the v5-5 → v6 Zest delta is considered in scope. Reviewed against the deployed counterpart, READ on mainnet: `SP2NK9P261ZN3ZG2M1DS68PCJCRDFZVFMPYA6ZEST.clmm-pool-v1` and `.clmm-router-v1` sources pulled from `api.mainnet.hiro.so/v2/contracts/source`. Reproduction: `verify_math.py` in this folder (all checks pass; `--live` re-reads pool 1). --- ## Result **No Critical or High findings. I could not break the central claim.** The bound (`zest-bound`), the tick window (`zest-ticks`), the native-STX custody path and the remainder/receipt accounting all hold up; the proofs are in *Verified safe* below. What is left is two low-severity items and one diagnostic item on the new split-swap leg. The pre-existing `zest-tick` −1 at exact bin edges is **not claimed** here — it was already reported (zero impact) and is reconfirmed in `verify_math.py` §2 so the poster can cross-check. | # | Severity | Where | One-liner | |---|---|---|---| | F-1 | Low | `leg-zest` L251-289, call sites L396 / L510 | a 1-raw-unit `zest` allocation makes the whole split swap revert instead of skipping the venue (the DLMM leg is guarded for exactly this, `dust-left` L1052 / L1065 / L1081) | | F-2 | Low (diagnostics) | `leg-zest` L251-289 | Zest pool errors u3004-u3007 pass through unchanged and collide with the router's own `u3004`…`u3007`, so a Zest failure is reported as a local validation error | | F-3 | Info (hardening) | `leg-zest` L251-289 vs `zest-bound` L1236 | the split entry points hand Zest a caller-supplied WAD price and never reconcile it with `limit-price`, although the contract can derive the bound for free | --- ## F-1 — Low — a dust `zest` allocation reverts the whole split swap **Function/lines.** `leg-zest` L251-289, reached from `swap-stx-for-usdcx` L396 and `swap-usdcx-for-stx` L510 with `(try! …)`. **Mechanism.** `leg-zest` always passes `allow-partial-fill = false` and `min-dy/min-dx = (amm-floor min-received)` — i.e. `0 → 1` (L216-220). On the pool side, `swap-x-for-y-route` L439-463 / `swap-y-for-x-route` L465-490 end with ``` (asserts! (or allow-partial-fill (is-eq left u0)) ERR-NOT-FILLED) ;; pool L454 / L480 ``` so any leg the walk does not consume in full is an error. For a 1-raw-unit input the fee alone zeroes the net input (`accept`, pool L638: `in = 1`, `fee = ceil(1 × 300000 / 1e8) = 1`, `net = 0`), `step-x-for-y` returns `NOOP`, `left` stays at 1 and the pool returns `ERR-NOT-FILLED u2010` → `clmm-router-v1.router-error` maps it to `u5001` → `leg-zest` propagates `(err u5001)` → `try!` at L396 / L510 aborts the **entire** split swap, including the Jing and DLMM legs that would have succeeded. **Failure scenario.** A front-end that splits a swap across venues and computes the Zest slice from a live quote can land on a 1-unit slice when the Zest book is nearly empty or the quote rounds down (the live pool is thin: bin 100 holds 29.17 STX / 0 USDCx and bin 125 holds 0 / 0.027 USDCx at the time of writing, so a quote can round to a handful of raw units). The user's transaction reverts with `u5001` ("insufficient liquidity") even though every other leg was fillable, and the natural retry (drop the Zest slice) has to be done off-chain. `dlmm-stage` already handles this class: L1065 skips the stage when `dust-left left limit`, and L1081 skips the leg when `dust-left plan limit`. `leg-zest` has no equivalent guard, so the two venues behave differently for the same dust input. **Fix.** Guard the call: ```clojure (define-private (leg-zest (amount uint) (min-received uint) (route { ticks: (list 32 int), price: uint }) (sell-stx bool) (limit uint)) (if (or (is-eq amount u0) (dust-left amount limit sell-stx)) (ok { in: u0, out: u0 }) …)) ``` `dust-left(1, limit, sell-stx)` is `true` for any realistic `limit` (`limit-min` already saturates to 0 for `leg ≤ ROUND_SLACK`), so a 1-unit slice is skipped rather than fatal. Do **not** simply flip to `allow-partial-fill = true`: the split-swap receipts at L427 / L448 / L541 / L562 account only for Jing's residual and DLMM's shortfall, so a partially filled Zest leg would leave an unreported remainder. --- ## F-2 — Low (diagnostics) — Zest error codes collide with the router's own **Function/lines.** `leg-zest` L251-289 (`(err e)` passthrough). `clmm-router-v1.router-error` remaps only `u2009/u2010/u2011/u2013 → u5002/u5001/u5003/u5004` and passes every other pool code through verbatim. The Zest pool's own codes are | Zest `clmm-pool-v1` | Jing router constant | |---|---| | `u3004` `ERR-TICK-OUT-OF-RANGE` (L30) | `u3004` `ERR_SPLIT_MISMATCH` (L90) | | `u3005` `ERR-AMOUNT-TOO-LARGE` | `u3005` `ERR_VAA_REQUIRED` | | `u3006` `ERR-MATH-OVERFLOW` | `u3006` `ERR_ZERO_LIMIT` | | `u3007` `ERR-TRANSFER-MISMATCH` | `u3007` `ERR_ZERO_MID` | **Failure scenario.** `swap-stx-for-usdcx` with a `zest-route.ticks` entry outside `TABLE-100.max-tick` (4458), or a bin frame that overflows, returns `u3004` / `u3006` from the *router*. Any client that switches on the router's documented codes reports "split amounts do not sum to `amount`" (or "VAA required") for what was actually a Zest failure, and the retry logic goes after the wrong input. Same class for `u3007` (`ERR_TRANSFER_MISMATCH` vs `ERR_ZERO_MID`), which is reachable through the pool's `pull` balance check (pool L561). **Fix.** Wrap the Zest call results into a dedicated range, e.g. `(err (+ u3100 e))` in `leg-zest` / `zest-swap`, and document `u3101+` as "Zest venue error, inner code − 3100". --- ## F-3 — Info (hardening) — the split leg's price bound is not reconciled with `limit-price` **Function/lines.** `leg-zest` L251-289 (uses `(get price route)` verbatim) vs `zest-bound` L1236-1247 (already in the contract, only used by `zest-stage` L1281). In a split swap the Zest leg is bounded only by `zest-route.price` (caller-supplied WAD price) and `amm-mins.zest`, which `amm-floor` turns into `1` when the caller leaves it at zero. The header documents `UINT-MAX` (x→y) / `u0` (y→x) as "unbounded", so an unset bound is indistinguishable on-chain from a deliberately loose one, and `min-usdcx-out` is then the only remaining guard on the newly added venue. **Fix** (cheap, reuses existing code): take `fee` from `get-fee-rates` and tighten the caller's price rather than trusting it: ```clojure (let ((b (zest-bound limit-price fee sell-stx))) … (if sell-stx (min (get price route) b) (max (get price route) b)) …) ``` or assert the caller's price is at least as tight as `zest-bound limit-price`, which turns a front-end bug into a clean revert instead of a worse-than-limit fill. (Note this matches how the other venues behave — `leg-stx`/`leg-usdcx` also trust `amm-mins` — so this is hardening of the new leg, not a demonstrated loss.) --- ## Verified safe — what I tried and why it holds ### 1. `zest-bound` is conservative in both directions (no worse-than-limit fill) `zest-bound` L1236-1247: * x→y: `bound = ⌊⌊1e26·(1e8−fee)/limit⌋/1e8⌋` ≤ `1e26·(1−f)/limit` * y→x: `c = ⌈1e34/limit⌉`, `bound = ⌈c/(1e8−fee)⌉` ≥ `1e26/(limit·(1−f))` Two floors (resp. two ceils) can only move the bound *toward* the user. `verify_math.py` §1 checks 7 fees × 13 limits × 2 directions against exact rational arithmetic: **0 loose bounds**. Derivation of why that is sufficient, with `P` = pool WAD price (STX per USDCx, both tokens 6 dp so raw and human ratios coincide): * the pool charges the fee on the input (`accept` L638: `fee = ⌈in·f⌉`, `net = in − fee`), so the user's effective price is `P_eff = (in/net)·P_trade ≥ P_trade/(1−f)`; * `step-x-for-y` L582-608 caps the **post-trade marginal** price at `frame-price-limit(max-price)` (and `step-y-for-x` floors it at `min-price`), so `P_trade ≤ max-price` (resp. `≥ min-price`); * for a constant-product-style bin the average price is between the pre-trade and post-trade marginal, hence still inside the bound; * ⇒ `P_eff ≥ (1e26/limit)/(1−f)·(1−f) = 1e26/limit` for x→y, and symmetrically for y→x. Integer fee rounding costs at most `1/(in·(1−f))` relative — the "1-raw-unit haircut" the header already discloses. ### 2. Orientation and tick↔bin mapping are right (checked against the live pool) * `get-bin-price(1, 0)` = `1e18` and `get-bin-price(1, t)` = `1e18·1.01^t`, so bin `t` covers `[1.01^t, 1.01^(t+1))` and `ZEST_LOG2_STEP = log2(1.01)·2^-60` is the correct step for bin-step 100 (`TABLE-100`, max-tick 4458). * The pool's WAD price is **x/y** (STX per USDCx): `current-sqrt = a + u/L` rises with `x`, and x→y caps the post-trade price with `max-price`. `mid-wad = 1e26/mid` is therefore the right inversion of `mid` (USDCx per STX × 1e8), and the live pool's liquidity at ticks 100-125 (price 2.7-3.5 STX/USDCx, STX ≈ $0.33) is where `mid = 3.3e7` puts it. * `zest-tick` L1167-1184 matches exact `⌊log₁.₀₁(P)⌋` at 3960/4005 sampled prices; the only disagreement is `t−1` at exact bin edges (already reported, no impact). * The ±4458 clamps are defensive only: `mid ≥ 1` bounds `tick(mid) ≤ 1851`, and `limit < 2^128` bounds `tick(min-price) ≥ −4236`, so neither clamp can be reached, and `end = tb ± 1` can never emit an out-of-range tick. ### 3. Native STX custody — the router really holds nothing * `SP1A27KFY4XERQCCRCARCYD1CC5N7M6688BSYADJ7.wstx` `transfer` is `(stx-transfer? amount sender recipient)` with no caller check, and `sender` is the pool's `tx-sender`. * `clmm-pool-v1` pulls with `transfer-in` L556-560: `(restrict-assets? owner ((with-stx amount)) (try! (pull token amount owner)))`, `owner = tx-sender`; and pays with `transfer-out` L566-570 `(as-contract? ((with-stx out)) (pay …))` to `tx-sender`. * The router never calls `as-contract`, so `tx-sender` is the user from the top-level call down through `clmm-router-v1` into the pool: STX leaves the user's balance straight to the pool and proceeds land straight back on the user. Confirmed by the harness's own balances check (`p1.st − p0.st === sIn`, `p0.us − p1.us === qs.out`) and by `pull`'s exact-balance assertion (L561). * A `zest-stage`/`leg-zest` error therefore cannot strand funds: the pool moves nothing before `out > 0` is known (L449 `(if (> out u0) (begin transfer-in transfer-out) (ok {dx:0,dy:0}))`), and Clarity rolls the inner call back when the router handles the `err`. ### 4. Remainder, receipts and `unsold` * Smart swaps: `out = gain(usdcx-before, usdcx-balance user)` L1355, and every leg pulls from and pays the user directly, so `unsold = amount − jing − zest − dlmm` (`cp-stage` L1319-1328) is exactly the balance the user never spent — no refund path is needed and none exists. * Split swaps: `unsold` (L427 / L448 / L541 / L562) is unchanged from v5-5 and stays correct for the Zest leg *because* `allow-partial-fill = false` (see F-1 for what breaks if that changes). * `zest-in`/`zest-out` are Zest's own `dx`/`dy`, correctly transposed for y→x (`in: (get dy r)`, `out: (get dx r)`, L1249-1278); the sum check `u3004` now includes `zest` on both entry points (L374-380, L488-494). * `zest = 0` short-circuits before the call (L253), so a stale `zest-route` is inert. ### 5. `mid` really cannot push a fill past `limit-price` `mid` only selects the first tick (`tm ∓ 2`, L1210-1234) and, via `zest-tick`, the stop tick. Bins below the market hold only x (`step-x-for-y` L585 short-circuits on `y = 0`), bins above hold only y and are each individually capped by `frame-price-limit(max-price)` (L743-760), so widening or shifting the window changes *how much* fills, never *at what worst price*. The walk is also capped at 32 ticks (L1210), which bounds cost; the harness's 32-tick + full-DLMM runs are the measured worst case. One nuance worth recording against the claim as worded: a stale-high `mid` does not make Zest fill *less* — it makes it fill further up the book, still inside `max-price`. The guarantee that matters ("never worse than `limit-price`") survives, but it is the bound, not `mid`, that provides it. --- ## Out of scope (not claimed) * `markets-stx-usdcx-jing-v6-4`, the ladder, DLMM/XYK/Velar legs, and Zest's own contracts. * `contracts/short/**` and the `stx-short` cluster present in the same commit. * Style, comment wording, error-code *numbering* beyond F-2's collision, and gas micro-optimisations (e.g. `end = tb ± 1` always appends one bin that cannot fill — provable from `sub-floor`/`accept`, but cost-only). #!/usr/bin/env python3 """Reproducible checks for the Zest leg of swap-router-stx-usdcx-jing-v6. Ports the contract's fixed-point helpers with Clarity uint128 semantics and compares them with exact math. `--live` re-reads Zest pool 1 from mainnet (read-only calls, no key). python3 verify_math.py # math checks only python3 verify_math.py --live # + pool params from api.mainnet.hiro.so """ import json, sys, urllib.request from decimal import Decimal, getcontext getcontext().prec = 60 U128 = 1 << 128 # constants, contract L1099-1112 FEE_ONE = 100_000_000 WAD_PRICE = 100_000_000_000_000_000_000_000_000 # 1e26 ONE = 1 << 60 TWO = 1 << 61 LOG2_WAD = 68_938_602_072_359_301_496 LOG2_STEP = 16_550_525_978_195_695 MAX_TICK = 4458 MARGIN = 2 FRAC_BITS = [1 << (59 - i) for i in range(16)] WALK = range(32) def cu(x): assert 0 <= x < U128, "uint128 overflow" return x def slog2(x): """zest-log2 (L1148): Q60 fixed point, int result.""" n = x.bit_length() - 1 m = x >> (n - 60) if n >= 60 else x << (60 - n) f = 0 for bit in FRAC_BITS: sq = cu((m * m) >> 60) if sq >= TWO: m, f = sq >> 1, f + bit else: m = sq return (n << 60) + f def stick(wad): """zest-tick (L1167): floor(log_1.01(wad/1e18)), clamped to +/-MAX_TICK.""" num = slog2(wad) - LOG2_WAD q = int(num / LOG2_STEP) t = q - 1 if (num < 0 and q * LOG2_STEP != num) else q return max(-MAX_TICK, min(MAX_TICK, t)) def sbound(limit, fee, sell_stx): """zest-bound (L1236). Returns the WAD price handed to clmm-pool-v1.""" if sell_stx: # upper bound (max-price) return cu(WAD_PRICE * (FEE_ONE - fee)) // limit // FEE_ONE c = (WAD_PRICE * FEE_ONE + limit - 1) // limit # ceil(1e34/limit) return (c + (FEE_ONE - fee - 1)) // (FEE_ONE - fee) def exact_bound(limit, fee, sell_stx): from fractions import Fraction as F f = F(fee, FEE_ONE) return (F(WAD_PRICE) * (1 - f) / limit) if sell_stx else (F(WAD_PRICE) / (limit * (1 - f))) def ticks(mid_wad, bound, up): """zest-ticks (L1210).""" tm, tb = stick(mid_wad), stick(bound) tick = tm - MARGIN if up else tm + MARGIN end = tb + 1 if up else tb - 1 out = [] for _ in WALK: if (tick > end) if up else (tick < end): break out.append(tick) tick += 1 if up else -1 return out def exact_tick(wad): """max t such that 1.01^t <= wad/1e18, exact.""" t = int((Decimal(wad) / Decimal(10) ** 18).ln() / Decimal("1.01").ln()) def le(t): if t >= 0: return 101 ** t * 10 ** 18 <= wad * 100 ** t return 101 ** 0 * 10 ** 18 * 100 ** (-t) <= wad * 100 ** 0 * 101 ** (-t) if False else \ Fraction(101, 100) ** t <= Fraction(wad, 10 ** 18) from fractions import Fraction while not le(t): t -= 1 while le(t + 1): t += 1 return t fails = 0 def chk(label, ok, detail=""): global fails fails += 0 if ok else 1 print(f" {'ok ' if ok else 'FAIL'} {label}{': ' + detail if detail else ''}") print("== 1 zest-bound is rounded against the user (never loose) ==") loose, n = [], 0 for fee in (0, 1, 100, 300_000, 1_000_000, 5_000_000, 9_999_999): for limit in (1, 1000, 10**5, 10**6, 5_500_000, 10**7, 3 * 10**7, 10**8, 3 * 10**8, 10**9, 10**10, 10**12, 10**15): n += 2 bx, by = sbound(limit, fee, True), sbound(limit, fee, False) ex, ey = exact_bound(limit, fee, True), exact_bound(limit, fee, False) if bx > ex: loose.append(("x->y", limit, fee, bx, float(ex))) if by < ey: loose.append(("y->x", limit, fee, by, float(ey))) chk(f"{n} (limit, fee, direction) points", not loose, f"loose bounds: {loose[:3]}") print("\n== 2 zest-tick accuracy vs exact floor(log_1.01) ==") from fractions import Fraction edges = [t for t in range(-100, 101) if stick(int(Decimal("1.01") ** t * Decimal(10) ** 18)) != exact_tick(int(Decimal("1.01") ** t * Decimal(10) ** 18))] inside = 0 for t in range(-400, 401): for offs in ("0.001", "0.25", "0.5", "0.75", "0.999"): p = Decimal("1.01") ** t * (1 + Decimal(offs) / 100) wad = int(p * Decimal(10) ** 18) if stick(wad) == exact_tick(wad): inside += 1 print(f" bin interiors: {inside}/4005 agree exactly") print(f" exact bin edges, t=-100..100: {len(edges)}/201 return t-1 (F-01 in the report)") chk("off-by-one confined to exact bin edges", all( stick(int(Decimal("1.01") ** t * Decimal(10) ** 18)) in (t, t - 1) for t in range(-100, 101))) print("\n== 3 tick window and the 32-tick cap ==") MARKET = 33_300_000 # mid = 0.333 USDCx/STX (STX ~ $0.33) mid = 10 ** 26 // MARKET for limit, label in ((MARKET, "limit at market"), (int(MARKET * 0.95), "5% loose"), (int(MARKET * 1.5), "tight: bound below market")): b = sbound(limit, 300_000, True) tl = ticks(mid, b, True) print(f" {label:<28} mid_t={stick(mid):>5} bound_t={stick(b):>5} n={len(tl):>2} " f"[{tl[0] if tl else '-'}..{tl[-1] if tl else '-'}]") print("\n== 4 reachable range of the tick arguments ==") print(" mid >= 1 (asserted) => mid_wad = 1e26/mid <= 1e26 => tick(mid) <= 1851") print(f" tick(1e26) = {stick(10**26)} (clamp at {MAX_TICK} is unreachable from mid)") print(" limit < 2^128 => min-price >= 1e26/2^128 => tick(min-price) >= " f"{stick(10**26 // 2**128)} (clamp at -{MAX_TICK} is unreachable from the bound)") chk("ZEST_MAX_TICK clamps are defensive only", stick(10**26) < MAX_TICK) print("\n== 5 pool minimum viable leg (why a dust Zest leg reverts) ==") for amt in (1, 2, 3, 4, 100): fee = -(-amt * 300_000 // 100_000_000) # ceil(in*fee_rate/1e8), accept L638 print(f" dx={amt:>3} raw -> fee=ceil({amt}*0.003)={fee} net={amt - fee}") print(" net = 0 => step returns NOOP => out = 0 => left != 0 =>") print(" clmm-pool-v1 L446: asserts (or allow-partial-fill (is-eq left u0)) ERR-NOT-FILLED u2010") print(" clmm-router-v1 router-error: u2010 -> u5001; leg-zest is try!'d (L396/L510) => whole swap reverts") print(f"\n{'ALL CHECKS PASSED' if not fails else str(fails) + ' CHECK(S) FAILED'}")