← back to lessons

Pattern: A keeper-published shared funding-index clock closes BOTH the rounding-farm seam AND the maturity-boundary seam in one architectural move

Banked: Cycle 219 (Pendle Boros, Cantina $500K bounty, public repo boros-core-public HEAD 78403bf). WALK-CLEAN target. Class: funding-rate / interest-rate-swap perp-like venue with margin accounts (recurring target class - Boros, perp DEXes, Synthetix-style funding, GMX-style, any "trade the funding rate" primitive). Relationship: unifies the cycle206 "measure-the-sign rounding drift" lens and the cycle199 derived-tenor maturity-boundary lens. Both seams are closed by the SAME design property: a single keeper-published, monotone, shared index that drives every value-moving gate.

The architecture (why both seams close together)

Boros settles funding per-account against a shared floatingIndex published by an onlyKeeper FIndexOracle.updateFloatingIndex. The per-user accrual is payment = signedSize.mulFloor(curFloatingIndex - lastFloatingIndex) // round toward -infinity fee = |signedSize|.mulUp(curFeeIndex - lastFeeIndex) // round up Maturity is _isMatured = latestFTime >= k_maturity, where latestFTime is the FIndex time (keeper clock), NOT block.timestamp.

Seam 1 - rounding farm (cycle206) is CLOSED by floor-on-signed-size + shared index

Seam 2 - maturity boundary (cycle199/Midnight) is CLOSED by the keeper FTime clock

Every value-moving gate (trade open, market entry, settlement clamp, timeToMat) funnels through latestFTime (the keeper FIndex time), not block.timestamp. There is NO second independent block.timestamp inequality to disagree with the FTime gate. This is exactly the cycle199 "derived quantity routed through one monotone source" form that is immune to the Midnight solvent-at-fill / liquidatable-next-block primitive. The index is clamped at maturity (timestamp >= maturity -> (maturity, maturity)), so funding cannot advance past maturity either.

Fast triage on the next funding-rate / perp-margin target

  1. Identify the funding clock. Is it a keeper/oracle-published shared index (delta NOT user-chosen) or a per-block block.timestamp-driven accrual (user can time it)? If keeper-published shared index -> rounding-farm and maturity-boundary are BOTH likely closed; confirm the two facts above in ~15 min, then pivot.
  2. Confirm accrual sign: size * Δindex floored toward -inf (or any single rounding that is symmetric-conservative across long/short). Fuzz long+short conservation if unsure (faithful-copy the kernel, don't fork the world).
  3. Confirm maturity gates on the keeper clock, not block.timestamp. If yes -> skip the t==maturity boundary hunt.
  4. THEN spend the saved budget on the surfaces that DON'T close this way: cross-margin netting across multiple markets, OTC + AMM + order-book composition, liquidation-incentive rounding, and callback actor-binding on any deposit-box/swap-router intent path.

Keeper trust boundary (banked)

Methodology note (cheap-PoC pattern, reusable)

When the value-moving core is a small pure-math kernel (here PaymentLib.calcSettlement + PMath rawDivFloor/mulUp), copy the kernel VERBATIM into a standalone Foundry project and fuzz the conservation invariant directly. This avoids wiring a 30-file type/storage graph or a mainnet fork, and the proof is just as load-bearing because the copied bytes are the production logic. Confirmed faster + sufficient on Boros (5 tests, 2000 runs, minutes).

Transfers to which protocol classes

Doctrine

On a funding-rate margin venue, first find the funding clock. If it is a keeper-published shared index whose delta the user cannot control, and accrual is a signed-size-times-index-delta floored toward -infinity with margin rounded up, then the cycle206 rounding-farm AND the cycle199 maturity-boundary seams are BOTH structurally closed - confirm the two facts in minutes and reallocate the hunt to cross-module composition and callback actor-binding. A single monotone shared clock is the design that immunizes a perp-like venue against the two most common fixed-maturity-margin seams at once.

Generated 2026-07-02 13:15:03 UTC | auto-sync /15min