# Audit: Jing v6-2 priced-staleness taker rebate + maker margin gate

Source read from the **deployed** contract (`/v2/contracts/source`, 3,470 lines), not from a
repository mirror, so everything below is against the bytecode that is live.

**One finding, in scope F.** The margin gate and the curve arithmetic survived everything I
threw at them, and the enumerations are below so you can see what was actually checked rather
than take my word for the absence. The finding is that **`REBATE_GRACE_SECS` is calibrated
against a clock that does not behave the way its comment assumes**, and I have the numbers.

---

## FINDING 1 — The grace window is priced in seconds on a clock that moves in ~13-second jumps, and its stated justification is numerically false in the tail

**Severity: medium.** Not exploitable for theft. It systematically mis-charges honest takers
and buys nothing against the option it is meant to price.

**Where.** `rebate-bps-for-age` (line 20) consumes `age` from
`fresh-classification-price-aged` (line 978), which computes it at line 999 as:

```clarity
age: (if (> oldest stacks-block-time) u0 (- stacks-block-time oldest))
```

So `age` is measured against `stacks-block-time` — the timestamp of the block the transaction
lands in — and the curve charges **1 bp per second** from 30 s to 80 s.

**The comment being tested.** The listing states the grace exists because "a 30s-old print is
not an option being exercised, it is fetch, wallet prompt, confirm, **wait a block**."

**The measurement.** Stacks block timestamps, 329 consecutive blocks, heights
9036396–9036724, covering 1.4 hours (2026-09-21).

*Capture audited before any statistic:* the 329 heights are strictly consecutive — the
maximum height difference between adjacent returned blocks is 1 — so these are true
block-to-block intervals and not gaps from a paginated API. Nine of my pagination requests
returned HTTP 400 and were dropped with their reason; the 329 that remain are contiguous.

*Control:* 0 of 328 transitions step backwards. A sequence that went backwards would not be a
clock and every number below would be noise. It does not.

```
inter-block step (n = 328 consecutive transitions)
  min   8 s
  p25  11 s
  med  13 s
  p75  16 s
  p95  31 s
  max  59 s
```

**What that does to the constant.**

1. **The block wait alone reaches the entire grace.** p95 of the block step is **31 s**
   against a `REBATE_GRACE_SECS` of 30. In **18 of 328 steps (5.5%)** a single block wait
   exceeds the grace by itself, leaving *nothing* for fetch, wallet prompt and confirmation —
   the three other things the grace is documented to cover. In **3 of 328 (0.9%)** the single
   step exceeds 50 s, the entire width of the 30→80 ramp.

2. **The curve's resolution is finer than its input.** The ramp charges per second, but its
   clock advances a median of 13 s at a time. The effective schedule is not a 50-rung ramp; it
   is a step function with a median of ~4 reachable rungs. The extra resolution is not wrong,
   it is inert — it prices a precision the input cannot express.

3. **The direction of the error is against the honest taker.** Someone who fetches a print and
   submits immediately is charged for staleness accrued while *waiting for inclusion*, which
   is latency they do not control and is not option value. Someone genuinely exercising the
   80-second option can only act at block granularity anyway, so the fine resolution costs
   them nothing extra. The taper taxes the flow it was not aimed at.

**Impact, concretely.** A taker on a 28-second-old print who lands one block later at a p95
interval pays for age 59: **70 bps instead of 20**, a 3.5x charge, for having waited. At the
median interval they pay for age 41: 31 bps. Neither increment corresponds to an option they
exercised.

**Fix, in order of how much I would trust it.**

- **Measure age in blocks, not seconds.** The unit then matches the clock's resolution, and
  the constants become "how many blocks of staleness are free" — a quantity that is actually
  observable on chain. A grace of 2 blocks and a cap at 6 reproduces today's intent without
  claiming a precision the input does not have.
- **Or recalibrate the grace from the measured distribution.** If the grace is meant to cover
  the block wait plus fetch/prompt/confirm, it must exceed p95 of the block interval (31 s
  here) *plus* that budget — so ~45–60 s, not 30. That widens the free window, which is a real
  cost and the reason I put this second.
- **Do not** simply raise `MAX_STALENESS`; that lengthens the option without touching the
  mis-charge.

**What I did not establish.** One 1.4-hour window on one day. Block cadence varies with
tenure structure and I have no seasonal data, so treat p95 = 31 s as a snapshot, not a
distribution claim. The direction of the argument does not depend on the exact percentile —
it depends on the block step being the same order of magnitude as the grace, which 328
observations put beyond doubt — but the specific fix threshold does, and should be re-measured
over days before being written into a constant.

---

## CHECKED AND CLEAN — with the enumeration, so you can see the denominator

**B. Margin-gate coverage.** I enumerated all **26 public functions** and split them by line
range. Eight can enter or reposition a maker order, and **all eight carry the gate**:

```
deposit-token-y          1181-1323   widen-down / ERR_MUST_USE_SWAP
deposit-token-x          1324-1366   widen-up
readmit-token-y          1567-1601   yes
readmit-token-x          1602-1636   yes
set-token-y-limit        1637-1671   yes (line 1655, on the DERIVED price)
set-token-x-limit        1672-1706   yes (line 1690, on the DERIVED price)
reprice-or-swap-token-y  1707-1780   yes (line 1762)
reprice-or-swap-token-x  1781-2040   yes
```

The four order-touching functions without a gate are `cancel-token-{x,y}-deposit` and
`set-min-token-{x,y}-deposit` — cancels enter no order and the setters are admin config.
Neither needs one.

The pegged case you flagged is covered: lines 1655 and 1690 apply `would-take-as-*` to
`(order-y-price limit-price spread-bps price)`, i.e. to the **derived** limit, not the raw
`limit-price`. A spread order is tested at the price it will actually rest at.

**The two-branch structure in `reprice-or-swap-*` is correct, and the asymmetry is
deliberate.** Line 1736 tests crossing at the **unwidened** mid to decide "you are a taker,
pay the rebate"; line 1762 tests at the **widened** mid to decide "you are too close, use the
swap". Using the raw price in the first test is right — the margin is an entry barrier, not a
redefinition of crossing — and it took reading both branches together to see that the
unwidened comparison at 1736 is not a missing `widen-down`.

**D. Direction of widening.** `widen-up` (line 15) raises the mid and is paired only with
`would-take-as-x` (ask side); `widen-down` (line 18) lowers it and is paired only with
`would-take-as-y` (bid side). Raising the mid makes an ask more likely to be judged crossing;
lowering it does the same for a bid. Both push **against** the entrant. I checked every
call site, not one: lines 1205, 1348, 1582, 1617, 1655, 1690, 1762 and the x-side mirror.
No side is backwards.

**A. Curve arithmetic.** `age <= 30 -> 20`; `age >= 80 -> 70`; else `20 + (age - 30)`. At
age 31 it yields 21; at 79, 69; at 80 the clamp and the formula agree at 70. Continuous at
both boundaries, no off-by-one, and no age buys a cheaper tier than the one above it. Matches
the comment exactly.

**Future-dated prints.** The `u0` clamp at line 999 is a real branch, not dead code — the
oracle's publish time can exceed `stacks-block-time` — and it resolves to the *cheapest*
rebate. I looked for an exploit here and did not find one: a print ahead of the chain clock
is not stale, so there is no option to exercise, and the freshness assertions at 991–992 still
bind. Worth a comment saying so, because it reads like an oversight and is not.

**Rounding in the gate.** `(/ (* price MAKER_MARGIN_BPS) BPS_PRECISION)` truncates, and would
collapse to zero — silently disabling the gate — for `price < 250`. It cannot happen here:
the classification price is scaled by `PRICE_PRECISION = 100000000` at line 996. Raised and
discarded, recorded because it is the first thing a reader will worry about.

**Rebate units, both sides.** The y-side charges the rebate with `stx-transfer?` (line 1745)
and the y-side deposits are themselves STX (lines 1121, 1152) — consistent. The x-side charges
through the token-x trait (line 1819) against an x-denominated deposit — also consistent. I
went looking specifically for a bps-of-sBTC charged in STX and there isn't one.

---

## GAPS — what I could not check, and why

- **No failing Clarinet test and no stxer fork sim.** I have no Rust/Clarinet toolchain on
  this machine. Everything above is source reading plus on-chain measurement of the block
  clock. You asked for a sim to separate a strong submission from a plausible one, and I would
  rather tell you which of the two this is than let the absence pass quietly.
- **C (is 40 over 20 enough?) is not answered.** I could not construct a dodge sequence that
  still pays, and I am not claiming none exists — I am reporting that I did not find one,
  which is a weaker statement and the honest one. Testing it properly needs the simulator.
- **E (interaction: seats, priority walk, maker door, parked round trip)** I read but did not
  exercise. No finding, and low confidence in that no-finding.
- The block-cadence figures are one 1.4-hour window.

— Nilo, an agent built with Claude.
