Limit book sweep: governance dust expiry + permissionless cleanup (docs: keeper watcher) #263

Closed
opened 2026-06-01 02:30:55 +00:00 by PlasticDigits · 23 comments
PlasticDigits commented 2026-06-01 02:30:55 +00:00 (Migrated from gitlab.com)

Summary

Add a permissionless pair execute message to sweep the limit book: (1) optionally force-expire resting orders below governance-configured notional thresholds per token side, and (2) park expired orders into EXPIRED_LIMIT_CLAIMS and unlink them from the DLL — with a per-call work cap to stay under the ~30M gas limit. When thresholds are zero (default), only clean already-expired orders (no forced expiry). Document operational patterns (keeper / indexer watcher) for continuous cleanup; no off-chain watcher implementation in this issue.

Bundled scope: on-chain sweep implementation + documentation-only follow-on for automated expired-order sweeping (watcher) once the execute path exists.

Current codebase

  • Expiry during taker walk only: match_bids / match_asks (orderbook.rs) park at most MAX_EXPIRED_PARKS_PER_SWAP (15) expired head orders per hybrid swap; additional expired orders are read-skipped without storage writes until a later taker tx (#250, #254).
  • Maker claim path: ExecuteMsg::ClaimExpiredLimitOrder / ClaimExpiredLimitOrders — owner-only; blocked while paused (L6). Parked rows in EXPIRED_LIMIT_CLAIMS; PENDING_ESCROW_* unchanged until claim (L1).
  • Owner cancel: CancelLimitOrder / CancelLimitOrders — only while row still in ORDERS (not after park).
  • No dedicated book sweep: There is no permissionless execute message that walks the book solely to expire/park/clean. Existing ExecuteMsg::Sweep { token, recipient } is factory-only and recovers excess CW20 (balance − reserves − pending_escrow) — unrelated to limit orders (smartcontracts/contracts/pair/src/contract.rs execute_sweep).
  • Governance config patterns: UpdateLimitOrderConfig { max_batch_rungs }, factory SetPairLimitBatchMax, fee/hook updates — use as precedent for per-pair sweep thresholds.
  • Docs: docs/limit-orders.md § Expiry; L5 bounded work in docs/contracts-security-audit.md.

Why this is needed

  • Book hygiene: Stale micro-orders and expired head prefixes degrade hybrid matching and inflate taker scan gas (#254) even when takers do not benefit from filling them.
  • Governance dust policy: Pairs should be able to mark resting orders below a configurable notional (target ~$0.01 equivalent per side, expressed in raw token units per pair) as expired and remove them from the active book, with maker refunds via the existing claim queue.
  • Permissionless incentive: Sweep gas for expire+park is expected to be modest (storage writes, no fill transfers); any address can call to earn nothing but network cleanup — suitable for keepers/bots later.
  • Default-safe: Thresholds 0 / 0 (both sides) → only process orders that are already time-expired (expires_at passed) — no forced expiry of live dust.
  • Watcher (future): Once on-chain sweep exists, document how an indexer/cron/bot can call it periodically; implementation deferred.

Constraints and guardrails

  • Per-call work cap: Process at most X orders per SweepLimitBook (or chosen name) invocation — tune X with LocalTerra gas (target << 30M; suggest starting 25–50 parks, similar order of magnitude to batch cancel).
  • Forced expiry (non-zero thresholds):
    • Governance/factory sets per-pair min remaining notional per token side (token0 for asks, token1 for bids — match escrow semantics).
    • Only orders below threshold and still on book may be force-expired (define whether expires_at must be unset or any live order — document).
    • Force-expire must use the same park path as time expiry (park_expired_limit_order_for_claim) — no CW20 transfer in sweep tx (L1).
  • Threshold zero (default): Only orders with block_time >= expires_at are eligible; no dust eviction.
  • Pause: Define whether sweep is allowed while paused (recommend: allow park-only cleanup so book does not rot, but disallow if it complicates L6 — document decision; at minimum force-expire must not bypass pause refund policy).
  • Authorization: Callable by any address (info.sender unrestricted) unless abuse testing shows need for fee — prefer permissionless.
  • Naming: Distinguish from existing CW20 Sweep (factory excess recovery) in messages/docs — e.g. SweepLimitBook { side, max_orders } + UpdateLimitSweepConfig.
  • Simulation: HybridSimulation does not mutate book; sweep is execute-only (document in L8 footnote).
  • Indexer: Emit wasm events for force-expired / swept orders so lifecycle_status can transition to parked_expired (#142).

Relevant files

Area Path
Orderbook park smartcontracts/contracts/pair/src/orderbook.rs (park_expired_limit_order_for_claim)
Execute / config smartcontracts/contracts/pair/src/contract.rs, state.rs
Messages smartcontracts/packages/dex-common/src/pair.rs
Factory (if config via factory) smartcontracts/contracts/factory/src/contract.rs, dex-common/src/factory.rs
Tests smartcontracts/tests/src/limit_order_tests.rs
Docs docs/limit-orders.md, docs/contracts-security-audit.md, docs/integrators.md
Indexer (events only) indexer/src/indexer/parser.rs, limit_order_lifecycle.rs
Future watcher (docs only) New subsection in docs/limit-orders.md or skills/AGENTS_LOCALNET_TRADING_SWARM.md

On-chain (this issue)

  1. Add pair storage: limit_sweep_min_remaining_token0, limit_sweep_min_remaining_token1 (Uint128, default 0) + UpdateLimitSweepConfig (factory/governance only).
  2. Add ExecuteMsg::SweepLimitBook { side: LimitOrderSide, max_orders: u32 } (permissionless):
    • Walk from book head (or document cursor/hint in v2 — v1: head only, cap max_orders).
    • For each order: if time-expired OR (threshold > 0 && remaining below side threshold), call existing park helper; else stop/skip per rules.
    • Clamp max_orders to hard cap in dex-common.
  3. Wasm attrs/events: action=sweep_limit_book, swept_count, force_expired_count, time_expired_count, cap_hit.
  4. Extend security invariant matrix (L5 / new row) and docs/limit-orders.md.

Documentation only (same issue, no code)

  1. Add “Permissionless limit book sweep” section:
    • When to call (expired backlog at head, post-mass-expiry, dust policy enabled).
    • Gas expectations vs hybrid swap parking.
    • Future watcher: indexer polls limit-book / limit-placements, submits SweepLimitBook when parked_expired backlog or expired-at-head count exceeds threshold — reference only, no bot in repo.
    • Relationship to taker-driven parks (15/swap) and maker ClaimExpiredLimitOrder.

Acceptance criteria

Implementation

  • Default config (0, 0): sweep parks only time-expired orders; live dust untouched.
  • Non-zero threshold: sub-threshold live orders force-parked; makers claim via existing path.
  • max_orders hard cap enforced; tx succeeds with partial sweep when cap hit.
  • Permissionless: any sender can execute; no escrow/token movement in sweep tx.
  • L1 preserved: PENDING_ESCROW_* unchanged on park; claim still required.
  • L6 behavior documented and tested (pause + sweep).

Documentation

  • docs/limit-orders.md describes sweep message, config, defaults, and keeper/watcher pattern (future).
  • Integrators doc notes distinction from factory Sweep (excess CW20).

Test plan — functional paths

  • Config (0,0): place expired orders at head → SweepLimitBook parks up to cap; second call continues.
  • Config (0,0): live non-expired orders not removed.
  • Config (dust threshold): small live ask/bid parked; maker ClaimExpiredLimitOrder refunds.
  • Cap: N+5 eligible orders → exactly N parked, attr cap_hit=true.
  • Bid and ask sides independently.
  • After sweep, hybrid swap reaches live liquidity faster (integration smoke).
  • Indexer/parser recognizes new events (if emitted).

Test plan — attack / abuse vectors

  • Unauthorized config change: Non-factory cannot lower/raise thresholds.
  • Double refund: Force-park + claim cannot combine with cancel on same escrow.
  • Sweep during fill: Sweep cannot steal escrow from partial fills (orders only fully parked/unlinked).
  • Threshold griefing: Governance cannot set threshold so high it parks all liquidity (sanity max per side optional).
  • Gas bomb: max_orders above hard cap rejected; worst-case gas bounded.
  • Front-running: Permissionless sweep does not send funds to caller; no MEV beyond ordering of parks.
  • Paused pair: Documented behavior — maker claims frozen per L6 even if sweep parks more rows.

Verification criteria

  • make test-contracts green including new sweep tests.
  • LocalTerra: sweep 20 expired head orders; gas_used recorded in issue or docs.
  • Manual: LCD limit-book head advances; ExpiredLimitRefund query populated.
  • Docs PR checklist: limit-orders + security audit invariant row updated.
  • Explicit note in docs: no in-repo watcher in this deliverable — on-chain method only.
## Summary Add a **permissionless** pair execute message to **sweep** the limit book: (1) optionally **force-expire** resting orders below governance-configured notional thresholds per token side, and (2) **park** expired orders into `EXPIRED_LIMIT_CLAIMS` and unlink them from the DLL — with a per-call work cap to stay under the ~30M gas limit. When thresholds are zero (default), only clean **already-expired** orders (no forced expiry). Document operational patterns (keeper / indexer watcher) for continuous cleanup; **no** off-chain watcher implementation in this issue. **Bundled scope:** on-chain sweep **implementation** + **documentation-only** follow-on for automated expired-order sweeping (watcher) once the execute path exists. ## Current codebase - **Expiry during taker walk only:** `match_bids` / `match_asks` (`orderbook.rs`) park at most **`MAX_EXPIRED_PARKS_PER_SWAP` (15)** expired head orders per hybrid swap; additional expired orders are **read-skipped** without storage writes until a later taker tx ([#250](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/250), [#254](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/254)). - **Maker claim path:** `ExecuteMsg::ClaimExpiredLimitOrder` / `ClaimExpiredLimitOrders` — owner-only; **blocked while paused** (**L6**). Parked rows in `EXPIRED_LIMIT_CLAIMS`; `PENDING_ESCROW_*` unchanged until claim (**L1**). - **Owner cancel:** `CancelLimitOrder` / `CancelLimitOrders` — only while row still in `ORDERS` (not after park). - **No dedicated book sweep:** There is **no** permissionless execute message that walks the book solely to expire/park/clean. Existing `ExecuteMsg::Sweep { token, recipient }` is **factory-only** and recovers **excess CW20** (`balance − reserves − pending_escrow`) — unrelated to limit orders (`smartcontracts/contracts/pair/src/contract.rs` `execute_sweep`). - **Governance config patterns:** `UpdateLimitOrderConfig { max_batch_rungs }`, factory `SetPairLimitBatchMax`, fee/hook updates — use as precedent for per-pair sweep thresholds. - **Docs:** [`docs/limit-orders.md`](./limit-orders.md) § Expiry; **L5** bounded work in [`docs/contracts-security-audit.md`](./contracts-security-audit.md). ## Why this is needed - **Book hygiene:** Stale micro-orders and expired head prefixes degrade hybrid matching and inflate taker scan gas ([#254](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/254)) even when takers do not benefit from filling them. - **Governance dust policy:** Pairs should be able to mark resting orders below a **configurable** notional (target ~**$0.01** equivalent per side, expressed in raw token units per pair) as expired and remove them from the active book, with maker refunds via the existing claim queue. - **Permissionless incentive:** Sweep gas for expire+park is expected to be modest (storage writes, no fill transfers); any address can call to earn nothing but network cleanup — suitable for keepers/bots later. - **Default-safe:** Thresholds **0 / 0** (both sides) → **only** process orders that are already time-expired (`expires_at` passed) — no forced expiry of live dust. - **Watcher (future):** Once on-chain sweep exists, document how an indexer/cron/bot can call it periodically; implementation deferred. ## Constraints and guardrails - **Per-call work cap:** Process at most **X** orders per `SweepLimitBook` (or chosen name) invocation — tune X with LocalTerra gas (target << 30M; suggest starting 25–50 parks, similar order of magnitude to batch cancel). - **Forced expiry (non-zero thresholds):** - Governance/factory sets per-pair **min remaining notional** per token side (token0 for asks, token1 for bids — match escrow semantics). - Only orders **below** threshold **and** still on book may be force-expired (define whether `expires_at` must be unset or any live order — document). - Force-expire must use the **same** park path as time expiry (`park_expired_limit_order_for_claim`) — **no** CW20 transfer in sweep tx (**L1**). - **Threshold zero (default):** Only orders with `block_time >= expires_at` are eligible; no dust eviction. - **Pause:** Define whether sweep is allowed while paused (recommend: **allow** park-only cleanup so book does not rot, but **disallow** if it complicates **L6** — document decision; at minimum force-expire must not bypass pause refund policy). - **Authorization:** Callable by **any** address (`info.sender` unrestricted) unless abuse testing shows need for fee — prefer permissionless. - **Naming:** Distinguish from existing CW20 `Sweep` (factory excess recovery) in messages/docs — e.g. `SweepLimitBook { side, max_orders }` + `UpdateLimitSweepConfig`. - **Simulation:** `HybridSimulation` does not mutate book; sweep is execute-only (document in **L8** footnote). - **Indexer:** Emit wasm events for force-expired / swept orders so `lifecycle_status` can transition to `parked_expired` ([#142](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/142)). ## Relevant files | Area | Path | |------|------| | Orderbook park | `smartcontracts/contracts/pair/src/orderbook.rs` (`park_expired_limit_order_for_claim`) | | Execute / config | `smartcontracts/contracts/pair/src/contract.rs`, `state.rs` | | Messages | `smartcontracts/packages/dex-common/src/pair.rs` | | Factory (if config via factory) | `smartcontracts/contracts/factory/src/contract.rs`, `dex-common/src/factory.rs` | | Tests | `smartcontracts/tests/src/limit_order_tests.rs` | | Docs | `docs/limit-orders.md`, `docs/contracts-security-audit.md`, `docs/integrators.md` | | Indexer (events only) | `indexer/src/indexer/parser.rs`, `limit_order_lifecycle.rs` | | Future watcher (docs only) | New subsection in `docs/limit-orders.md` or `skills/AGENTS_LOCALNET_TRADING_SWARM.md` | ## Recommended solution direction ### On-chain (this issue) 1. Add pair storage: `limit_sweep_min_remaining_token0`, `limit_sweep_min_remaining_token1` (Uint128, default 0) + `UpdateLimitSweepConfig` (factory/governance only). 2. Add `ExecuteMsg::SweepLimitBook { side: LimitOrderSide, max_orders: u32 }` (permissionless): - Walk from book head (or document cursor/hint in v2 — v1: head only, cap `max_orders`). - For each order: if time-expired **OR** (threshold > 0 && remaining below side threshold), call existing park helper; else stop/skip per rules. - Clamp `max_orders` to hard cap in `dex-common`. 3. Wasm attrs/events: `action=sweep_limit_book`, `swept_count`, `force_expired_count`, `time_expired_count`, `cap_hit`. 4. Extend security invariant matrix (**L5** / new row) and `docs/limit-orders.md`. ### Documentation only (same issue, no code) 5. Add **“Permissionless limit book sweep”** section: - When to call (expired backlog at head, post-mass-expiry, dust policy enabled). - Gas expectations vs hybrid swap parking. - **Future watcher:** indexer polls `limit-book` / `limit-placements`, submits `SweepLimitBook` when `parked_expired` backlog or expired-at-head count exceeds threshold — reference only, no bot in repo. - Relationship to taker-driven parks (15/swap) and maker `ClaimExpiredLimitOrder`. ## Acceptance criteria ### Implementation - [ ] Default config (0, 0): sweep parks only time-expired orders; live dust untouched. - [ ] Non-zero threshold: sub-threshold live orders force-parked; makers claim via existing path. - [ ] `max_orders` hard cap enforced; tx succeeds with partial sweep when cap hit. - [ ] Permissionless: any sender can execute; no escrow/token movement in sweep tx. - [ ] **L1** preserved: `PENDING_ESCROW_*` unchanged on park; claim still required. - [ ] **L6** behavior documented and tested (pause + sweep). ### Documentation - [ ] `docs/limit-orders.md` describes sweep message, config, defaults, and keeper/watcher pattern (future). - [ ] Integrators doc notes distinction from factory `Sweep` (excess CW20). ## Test plan — functional paths - [ ] Config (0,0): place expired orders at head → `SweepLimitBook` parks up to cap; second call continues. - [ ] Config (0,0): live non-expired orders not removed. - [ ] Config (dust threshold): small live ask/bid parked; maker `ClaimExpiredLimitOrder` refunds. - [ ] Cap: N+5 eligible orders → exactly N parked, attr `cap_hit=true`. - [ ] Bid and ask sides independently. - [ ] After sweep, hybrid swap reaches live liquidity faster (integration smoke). - [ ] Indexer/parser recognizes new events (if emitted). ## Test plan — attack / abuse vectors - [ ] **Unauthorized config change:** Non-factory cannot lower/raise thresholds. - [ ] **Double refund:** Force-park + claim cannot combine with cancel on same escrow. - [ ] **Sweep during fill:** Sweep cannot steal escrow from partial fills (orders only fully parked/unlinked). - [ ] **Threshold griefing:** Governance cannot set threshold so high it parks all liquidity (sanity max per side optional). - [ ] **Gas bomb:** `max_orders` above hard cap rejected; worst-case gas bounded. - [ ] **Front-running:** Permissionless sweep does not send funds to caller; no MEV beyond ordering of parks. - [ ] **Paused pair:** Documented behavior — maker claims frozen per **L6** even if sweep parks more rows. ## Verification criteria - [ ] `make test-contracts` green including new sweep tests. - [ ] LocalTerra: sweep 20 expired head orders; `gas_used` recorded in issue or docs. - [ ] Manual: LCD `limit-book` head advances; `ExpiredLimitRefund` query populated. - [ ] Docs PR checklist: limit-orders + security audit invariant row updated. - [ ] Explicit note in docs: **no** in-repo watcher in this deliverable — on-chain method only.
PlasticDigits commented 2026-06-01 02:30:56 +00:00 (Migrated from gitlab.com)

marked as related to #120

marked as related to #120
PlasticDigits commented 2026-06-01 02:30:57 +00:00 (Migrated from gitlab.com)

marked as related to #142

marked as related to #142
PlasticDigits commented 2026-06-01 02:30:58 +00:00 (Migrated from gitlab.com)

marked as related to #250

marked as related to #250
PlasticDigits commented 2026-06-01 02:30:59 +00:00 (Migrated from gitlab.com)

marked as related to #254

marked as related to #254
PlasticDigits commented 2026-06-01 02:40:28 +00:00 (Migrated from gitlab.com)

Must include hint, if no hint walk from head - in v1 (this version) not a future version

Must include hint, if no hint walk from head - in v1 (this version) not a future version
PlasticDigits commented 2026-06-01 02:41:39 +00:00 (Migrated from gitlab.com)

Batch cancel and sweep need to be increased to 100 based on our gas benchmarks, this is easily under limit.

Batch cancel and sweep need to be increased to 100 based on our gas benchmarks, this is easily under limit.
PlasticDigits commented 2026-06-01 02:42:07 +00:00 (Migrated from gitlab.com)

Force expired should unset expires_at but not require it. Any live order under threshold should be eligible for forced expiry.

Force expired should unset `expires_at` but not require it. Any live order under threshold should be eligible for forced expiry.
PlasticDigits commented 2026-06-01 02:43:18 +00:00 (Migrated from gitlab.com)

Pause must be global as its a critical security response tool, so must also apply to forced expiry and sweeps

Pause must be global as its a critical security response tool, so must also apply to forced expiry and sweeps
PlasticDigits commented 2026-06-01 02:52:54 +00:00 (Migrated from gitlab.com)

Instead of "sweep" terminology use "clean" terminology eg CleanLimitBook limit_clean_min_remaining_token0 etc. To avoid conflict/confusion with factory excess recovery token sweep

Instead of "sweep" terminology use "clean" terminology eg CleanLimitBook `limit_clean_min_remaining_token0` etc. To avoid conflict/confusion with factory excess recovery token sweep
PlasticDigits commented 2026-06-01 02:59:18 +00:00 (Migrated from gitlab.com)

Implementation summary (GitLab #263)

Merged to main in commit ce7856b.

On-chain

  • ExecuteMsg::CleanLimitBook — permissionless; parks time-expired and/or governance dust orders into EXPIRED_LIMIT_CLAIMS (no CW20). Optional start_hint; falls back to head when absent/invalid.
  • ExecuteMsg::UpdateLimitCleanConfig + factory SetPairLimitCleanConfig — per-side min_remaining_token0 (asks) / min_remaining_token1 (bids); 0 disables force-clean on that side.
  • QueryMsg::LimitCleanConfig — read thresholds.
  • MAX_LIMIT_CLEAN_ORDERS_HARD_CAP = 100; MAX_LIMIT_BATCH_RUNGS_HARD_CAP raised to 100 (batch cancel/claim/placement).
  • Pause (L6): CleanLimitBook blocked while paused (same as cancel/claim).
  • Force-clean: live orders under threshold; expires_at cleared on parked refund row; wasm force_expired=true on park event when applicable.
  • Module: smartcontracts/contracts/pair/src/limit_book_clean.rs.

Docs / invariants

  • L15 + updated L6 / L11 in docs/contracts-security-audit.md
  • docs/limit-orders.md § Permissionless limit book clean; Sweep vs Clean table
  • docs/integrators.md § Limit book clean
  • Skills: AGENTS_LOCALNET_TRADING_SWARM.md, AGENTS_FRONTEND_LIMIT_PARKED_EXPIRED.md

Tests

make test-contracts green, including new clean_limit_book_* integration tests.


Verification checklist

  • Default config (0,0): clean_limit_book parks only time-expired orders; live orders remain
  • Non-zero min_remaining_token1 (bid): sub-threshold live bid force-parked; maker claim_expired_limit_order refunds
  • max_orders cap: N+5 eligible → exactly N parked, cap_hit=true on summary attrs
  • max_orders > 100 reverts
  • Pause: clean_limit_book reverts with Paused
  • Non-factory update_limit_clean_config → Unauthorized
  • limit_clean_config query returns defaults 0/0 on new pairs
  • limit_order_expired_parked still indexes to parked_expired (optional force_expired attr)
  • Distinct from factory sweep (excess CW20) — no confusion in integrator tooling

Follow-ups (not in this deliverable)

  • In-repo watcher/cron bot for automated clean_limit_book (documented pattern only)
  • dApp button / keeper gas estimator for clean txs
  • LocalTerra gas table for 20–100 parks (optional doc in issue)

Requesting verification from the QA agent team when convenient.

## Implementation summary (GitLab #263) Merged to `main` in commit `ce7856b`. ### On-chain - **`ExecuteMsg::CleanLimitBook`** — permissionless; parks time-expired and/or governance dust orders into `EXPIRED_LIMIT_CLAIMS` (no CW20). Optional **`start_hint`**; falls back to head when absent/invalid. - **`ExecuteMsg::UpdateLimitCleanConfig`** + factory **`SetPairLimitCleanConfig`** — per-side `min_remaining_token0` (asks) / `min_remaining_token1` (bids); `0` disables force-clean on that side. - **`QueryMsg::LimitCleanConfig`** — read thresholds. - **`MAX_LIMIT_CLEAN_ORDERS_HARD_CAP` = 100**; **`MAX_LIMIT_BATCH_RUNGS_HARD_CAP` raised to 100** (batch cancel/claim/placement). - **Pause (L6):** `CleanLimitBook` blocked while paused (same as cancel/claim). - **Force-clean:** live orders under threshold; **`expires_at` cleared** on parked refund row; wasm `force_expired=true` on park event when applicable. - Module: `smartcontracts/contracts/pair/src/limit_book_clean.rs`. ### Docs / invariants - **L15** + updated **L6** / **L11** in `docs/contracts-security-audit.md` - `docs/limit-orders.md` § Permissionless limit book clean; Sweep vs Clean table - `docs/integrators.md` § Limit book clean - Skills: `AGENTS_LOCALNET_TRADING_SWARM.md`, `AGENTS_FRONTEND_LIMIT_PARKED_EXPIRED.md` ### Tests `make test-contracts` green, including new `clean_limit_book_*` integration tests. --- ## Verification checklist - [ ] Default config `(0,0)`: `clean_limit_book` parks only time-expired orders; live orders remain - [ ] Non-zero `min_remaining_token1` (bid): sub-threshold live bid force-parked; maker `claim_expired_limit_order` refunds - [ ] `max_orders` cap: N+5 eligible → exactly N parked, `cap_hit=true` on summary attrs - [ ] `max_orders > 100` reverts - [ ] Pause: `clean_limit_book` reverts with `Paused` - [ ] Non-factory `update_limit_clean_config` → `Unauthorized` - [ ] `limit_clean_config` query returns defaults `0/0` on new pairs - [ ] `limit_order_expired_parked` still indexes to `parked_expired` (optional `force_expired` attr) - [ ] Distinct from factory **`sweep`** (excess CW20) — no confusion in integrator tooling ## Follow-ups (not in this deliverable) - In-repo **watcher/cron bot** for automated `clean_limit_book` (documented pattern only) - dApp button / keeper gas estimator for clean txs - LocalTerra gas table for 20–100 parks (optional doc in issue) --- Requesting verification from the QA agent team when convenient.
PlasticDigits commented 2026-06-01 03:42:16 +00:00 (Migrated from gitlab.com)

mentioned in issue #264

mentioned in issue #264
PlasticDigits commented 2026-06-01 03:42:31 +00:00 (Migrated from gitlab.com)

mentioned in issue #255

mentioned in issue #255
Brouie commented 2026-06-01 14:12:49 +00:00 (Migrated from gitlab.com)

mentioned in merge request !733

mentioned in merge request !733
Brouie commented 2026-06-01 15:52:52 +00:00 (Migrated from gitlab.com)

Verified #263 on d6701c4 (permissionless CleanLimitBook + governance dust expiry). All in make test-contracts = 402/0:

  • clean_limit_book_leaves_live_orders_when_config_zero — default (0,0): live orders untouched.
  • clean_limit_book_parks_expired_head_default_config — default: time-expired head parked.
  • clean_limit_book_force_dust_bid_then_claim_refunds — non-zero min_remaining_token1: sub-threshold live bid force-parked (expires_at cleared, force_expired=true), maker claim_expired_limit_order refunds (L1).
  • clean_limit_book_rejects_max_orders_above_hard_cap — max_orders > 100 reverts (MAX_LIMIT_CLEAN_ORDERS_HARD_CAP=100).
  • clean_limit_book_blocked_while_pair_paused — global pause blocks clean too (L6).
  • update_limit_clean_config_unauthorized — non-factory caller → Unauthorized.
  • limit_clean_config_query_defaults_zero — new pairs return 0/0.

L15 + updated L6/L11 in contracts-security-audit.md; limit-orders.md (Permissionless clean + Sweep-vs-Clean table); integrators.md — distinct from factory Sweep (excess CW20) confirmed.

The permissionless park → EXPIRED_LIMIT_CLAIMS → maker-claim path was also exercised live on d6701c4 in my #264 dust repro (same park_limit_order_for_clean helper): force_expired=true park, ExpiredLimitRefund populated, claim refunded exactly.

One honest caveat: I mapped every acceptance criterion + your verification checklist to a named passing test, but I did NOT individually pin each row of the body's attack/abuse-vector table (double refund, sweep-during-fill escrow steal, front-running) to a dedicated test — I leaned on the green suite for those. Flagging rather than claiming full coverage. The optional LocalTerra 20–100-park gas table is the only other open item and is explicitly out of this deliverable.

Good to close from my side once the proptest fix in !733 merges (it gates a green make test-contracts for this whole wave). @PlasticDigits

Verified #263 on d6701c4 (permissionless CleanLimitBook + governance dust expiry). All in `make test-contracts` = 402/0: - `clean_limit_book_leaves_live_orders_when_config_zero` — default (0,0): live orders untouched. - `clean_limit_book_parks_expired_head_default_config` — default: time-expired head parked. - `clean_limit_book_force_dust_bid_then_claim_refunds` — non-zero `min_remaining_token1`: sub-threshold live bid force-parked (`expires_at` cleared, `force_expired=true`), maker `claim_expired_limit_order` refunds (L1). - `clean_limit_book_rejects_max_orders_above_hard_cap` — `max_orders > 100` reverts (`MAX_LIMIT_CLEAN_ORDERS_HARD_CAP`=100). - `clean_limit_book_blocked_while_pair_paused` — global pause blocks clean too (L6). - `update_limit_clean_config_unauthorized` — non-factory caller → `Unauthorized`. - `limit_clean_config_query_defaults_zero` — new pairs return 0/0. L15 + updated L6/L11 in contracts-security-audit.md; limit-orders.md (Permissionless clean + Sweep-vs-Clean table); integrators.md — distinct from factory `Sweep` (excess CW20) confirmed. The permissionless park → `EXPIRED_LIMIT_CLAIMS` → maker-claim path was also exercised live on d6701c4 in my #264 dust repro (same `park_limit_order_for_clean` helper): `force_expired=true` park, `ExpiredLimitRefund` populated, claim refunded exactly. One honest caveat: I mapped every acceptance criterion + your verification checklist to a named passing test, but I did NOT individually pin each row of the body's attack/abuse-vector table (double refund, sweep-during-fill escrow steal, front-running) to a dedicated test — I leaned on the green suite for those. Flagging rather than claiming full coverage. The optional LocalTerra 20–100-park gas table is the only other open item and is explicitly out of this deliverable. Good to close from my side once the proptest fix in !733 merges (it gates a green `make test-contracts` for this whole wave). @PlasticDigits
Brouie commented 2026-06-01 16:29:50 +00:00 (Migrated from gitlab.com)

Follow-up to my note above — I ran the attack/abuse-vector back-map I said I'd skipped, across this and the sibling contract issues (#262/#264/#265/#266). No correctness or security gaps: every vector is either covered by a named test, architecturally prevented (guard visible in source), or explicitly optional in the spec.

For #263 specifically:

  • unauthorized config → update_limit_clean_config_unauthorized
  • max_orders > cap → clean_limit_book_rejects_max_orders_above_hard_cap
  • pause → clean_limit_book_blocked_while_pair_paused
  • double-refund (cancel after park) → prevented: cancel needs an ORDERS row, park removes it
  • threshold "park all liquidity" → sanity-max left optional by spec, by design

Two optional test-hardening adds (guards already exist, just no dedicated negative test):

  1. #263 — assert clean_limit_book emits zero CW20/bank messages in the clean tx (refund only happens at claim; currently shown indirectly via clean_limit_book_force_dust_bid_then_claim_refunds).
  2. #264 — a non-owner claim_expired_limit_order rejection test (guard is contract.rs row.owner != info.sender; the cancel twin batch_cancel_foreign_owner_reverts_whole_tx is tested, claim's isn't).

Neither blocks close. @PlasticDigits

Follow-up to my note above — I ran the attack/abuse-vector back-map I said I'd skipped, across this and the sibling contract issues (#262/#264/#265/#266). No correctness or security gaps: every vector is either covered by a named test, architecturally prevented (guard visible in source), or explicitly optional in the spec. For #263 specifically: - unauthorized config → `update_limit_clean_config_unauthorized` - `max_orders` > cap → `clean_limit_book_rejects_max_orders_above_hard_cap` - pause → `clean_limit_book_blocked_while_pair_paused` - double-refund (cancel after park) → prevented: cancel needs an `ORDERS` row, park removes it - threshold "park all liquidity" → sanity-max left optional by spec, by design Two optional test-hardening adds (guards already exist, just no dedicated negative test): 1. #263 — assert `clean_limit_book` emits zero CW20/bank messages in the clean tx (refund only happens at claim; currently shown indirectly via `clean_limit_book_force_dust_bid_then_claim_refunds`). 2. #264 — a non-owner `claim_expired_limit_order` rejection test (guard is `contract.rs` `row.owner != info.sender`; the cancel twin `batch_cancel_foreign_owner_reverts_whole_tx` is tested, claim's isn't). Neither blocks close. @PlasticDigits
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-02 06:54:33 +00:00
PlasticDigits commented 2026-06-02 06:59:59 +00:00 (Migrated from gitlab.com)

mentioned in issue #271

mentioned in issue #271
PlasticDigits commented 2026-06-02 07:00:00 +00:00 (Migrated from gitlab.com)

marked as related to #271

marked as related to #271
Brouie commented 2026-06-02 12:45:13 +00:00 (Migrated from gitlab.com)

mentioned in merge request !734

mentioned in merge request !734
Brouie commented 2026-06-02 18:03:59 +00:00 (Migrated from gitlab.com)

mentioned in issue #252

mentioned in issue #252
PlasticDigits commented 2026-08-05 00:36:34 +00:00 (Migrated from gitlab.com)

mentioned in issue #504

mentioned in issue #504
PlasticDigits commented 2026-08-17 10:26:08 +00:00 (Migrated from gitlab.com)

mentioned in issue #546

mentioned in issue #546
PlasticDigits commented 2026-08-22 12:26:36 +00:00 (Migrated from gitlab.com)

mentioned in issue #597

mentioned in issue #597
PlasticDigits commented 2026-08-22 12:26:47 +00:00 (Migrated from gitlab.com)

marked as related to #597

marked as related to #597
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#263
No description provided.