Fix: Harden hybrid max_spread no-belief guards (GitLab #273 followups) #307

Closed
opened 2026-06-05 04:08:26 +00:00 by PlasticDigits · 19 comments
PlasticDigits commented 2026-06-05 04:08:26 +00:00 (Migrated from gitlab.com)

Current codebase

GitLab #273 added a no-belief hybrid guard in smartcontracts/packages/dex-common/src/max_spread.rs:

  • Book-leg shortfall vs pool realized net rate (pool_net_return / pool_input) folds into spread numerator when both pool_input > 0 and book_input > 0.
  • Pure-book hybrid (pool_input == 0) is explicitly unbounded by this metric — deferred to belief_price / min_receive.
  • Split is caller-controlled: pool_input + book_input == offer_amount (enforced in pair/router) but callers can set extreme splits.

Remaining gaps (follow-ups to #273):

  1. Reference quality: When pool_input is tiny but non-zero, pool_net_return / pool_input is a noisy/unstable reference; attacker or mistaken router can pass inflated book legs.
  2. Material pool leg: No floor requiring meaningful pool participation when book_input > 0 on the no-belief path — e.g. pool_net_return > 0 or pool_input >= min when book leg present.

Tests in max_spread.rs and limit_order_tests.rs (#273 PoC section) document current behavior.

Why this is needed

Without a material-pool-leg floor, integrators can route almost entire size through a degraded book while using a dust pool leg to manipulate the reference rate denominator/numerator asymmetry. Users relying on default max_spread (1%) without belief_price may accept bad hybrid fills.

Constraints / guardrails

  • Preserve byte-for-byte pool-only and book_input == 0 behavior (#197 metric).
  • Pure-book (pool_input == 0) remains out of scope for this guard — still needs belief_price or min_receive.
  • Coordinate with router validation (router/src/contract.rs hybrid split checks).
  • Any new minimum must use existing uint128 patterns; no floating dust.
  • Update docs/contracts-security-audit.md hybrid invariants.
  • Frontend simulation should surface new reject reason.

Relevant files

Area Path
Guard smartcontracts/packages/dex-common/src/max_spread.rs
Pair execute smartcontracts/contracts/pair/src/contract.rs
Router smartcontracts/contracts/router/src/contract.rs
Tests max_spread.rs unit tests, limit_order_tests.rs (#273)
Docs docs/contracts-security-audit.md
  1. Define MIN_POOL_INPUT_FOR_BOOK_HYBRID (or ratio vs offer_amount) in dex-common — governance-tunable or constant.
  2. In check_max_spread no-belief branch: when book_input > 0, require pool_input >= min and pool_net_return > 0 before applying book shortfall; else Err(MaxSpreadViolation) or dedicated error at execute layer.
  3. Optionally cap reference rate sensitivity when pool_input below threshold — treat as pure-book (reject without belief).
  4. Add router-level precheck mirroring pair.
  5. PoC regression test from #273 must fail after fix.

Acceptance criteria

  • Hybrid with book_input > 0 and dust pool_input rejected under default max_spread without belief.
  • Legit hybrid with material pool leg still passes (#273 “fair book” test).
  • Pool-only and zero-book hybrids unchanged.
  • Clear contract error attribute for integrators.
  • Security audit doc updated.

Test plan (all paths)

Case Expected
pool_only Pass/fail unchanged vs #197
hybrid book=0 Unchanged
hybrid material pool + fair book Pass
hybrid tiny pool_input + bad book Reject
hybrid pool_input=0 pure book Unchanged (needs belief)
belief_price path Unaffected

Attack / abuse / hack vectors

Vector Test
Dust pool leg + toxic book Rejected
Inflate pool_net_return via manipulation Still bounded by pool swap math + min pool input
Router multihop split mismatch Router rejects before pair

Verification criteria

  • make test-contracts green.
  • #273 PoC test inverted (must reject).
  • HybridSimulation matches execute revert.
## Current codebase GitLab **#273** added a no-belief hybrid guard in `smartcontracts/packages/dex-common/src/max_spread.rs`: - Book-leg **shortfall** vs pool realized net rate (`pool_net_return / pool_input`) folds into spread numerator when **both** `pool_input > 0` and `book_input > 0`. - **Pure-book** hybrid (`pool_input == 0`) is explicitly **unbounded** by this metric — deferred to `belief_price` / `min_receive`. - Split is **caller-controlled**: `pool_input + book_input == offer_amount` (enforced in pair/router) but callers can set extreme splits. Remaining gaps (follow-ups to #273): 1. **Reference quality:** When `pool_input` is tiny but non-zero, `pool_net_return / pool_input` is a noisy/unstable reference; attacker or mistaken router can pass inflated book legs. 2. **Material pool leg:** No floor requiring meaningful pool participation when `book_input > 0` on the no-belief path — e.g. `pool_net_return > 0` or `pool_input >= min` when book leg present. Tests in `max_spread.rs` and `limit_order_tests.rs` (#273 PoC section) document current behavior. ## Why this is needed Without a material-pool-leg floor, integrators can route almost entire size through a degraded book while using a dust pool leg to manipulate the reference rate denominator/numerator asymmetry. Users relying on default `max_spread` (1%) without `belief_price` may accept bad hybrid fills. ## Constraints / guardrails - Preserve byte-for-byte **pool-only** and **book_input == 0** behavior (#197 metric). - Pure-book (`pool_input == 0`) remains out of scope for this guard — still needs `belief_price` or `min_receive`. - Coordinate with router validation (`router/src/contract.rs` hybrid split checks). - Any new minimum must use existing uint128 patterns; no floating dust. - Update `docs/contracts-security-audit.md` hybrid invariants. - Frontend simulation should surface new reject reason. ## Relevant files | Area | Path | |------|------| | Guard | `smartcontracts/packages/dex-common/src/max_spread.rs` | | Pair execute | `smartcontracts/contracts/pair/src/contract.rs` | | Router | `smartcontracts/contracts/router/src/contract.rs` | | Tests | `max_spread.rs` unit tests, `limit_order_tests.rs` (#273) | | Docs | `docs/contracts-security-audit.md` | ## Recommended direction 1. Define `MIN_POOL_INPUT_FOR_BOOK_HYBRID` (or ratio vs `offer_amount`) in `dex-common` — governance-tunable or constant. 2. In `check_max_spread` no-belief branch: when `book_input > 0`, require `pool_input >= min` **and** `pool_net_return > 0` before applying book shortfall; else `Err(MaxSpreadViolation)` or dedicated error at execute layer. 3. Optionally cap reference rate sensitivity when `pool_input` below threshold — treat as pure-book (reject without belief). 4. Add router-level precheck mirroring pair. 5. PoC regression test from #273 must **fail** after fix. ## Acceptance criteria - [ ] Hybrid with `book_input > 0` and dust `pool_input` rejected under default `max_spread` without belief. - [ ] Legit hybrid with material pool leg still passes (#273 “fair book” test). - [ ] Pool-only and zero-book hybrids unchanged. - [ ] Clear contract error attribute for integrators. - [ ] Security audit doc updated. ## Test plan (all paths) | Case | Expected | |------|----------| | pool_only | Pass/fail unchanged vs #197 | | hybrid book=0 | Unchanged | | hybrid material pool + fair book | Pass | | hybrid tiny pool_input + bad book | Reject | | hybrid pool_input=0 pure book | Unchanged (needs belief) | | belief_price path | Unaffected | ## Attack / abuse / hack vectors | Vector | Test | |--------|------| | Dust pool leg + toxic book | Rejected | | Inflate `pool_net_return` via manipulation | Still bounded by pool swap math + min pool input | | Router multihop split mismatch | Router rejects before pair | ## Verification criteria - `make test-contracts` green. - #273 PoC test inverted (must reject). - `HybridSimulation` matches execute revert.
PlasticDigits commented 2026-06-05 04:08:27 +00:00 (Migrated from gitlab.com)

marked as related to #273

marked as related to #273
ghost1 commented 2026-06-05 09:58:48 +00:00 (Migrated from gitlab.com)

mentioned in commit 6cf3d7a4dd

mentioned in commit 6cf3d7a4dd4fe2e25826578186103539e7b59651
PlasticDigits commented 2026-06-05 09:59:16 +00:00 (Migrated from gitlab.com)

Implementation pushed on branch cursor/gitlab-issue-workflow-dc06. Material pool leg guard (#307) added on top of #273 book-shortfall in dex_common::max_spread: declared pool_input must be ≥10% of offer when book_input > 0 without belief_price. Pure-book (declared_pool_input = 0) unchanged.

Verification: make test-contracts PASS; cargo test -p cl8y-dex-tests hybrid_no_belief PASS; frontend swapMaxSpread.test.ts PASS.

MR link will be posted when created.

Implementation pushed on branch `cursor/gitlab-issue-workflow-dc06`. Material pool leg guard (#307) added on top of #273 book-shortfall in `dex_common::max_spread`: declared `pool_input` must be ≥10% of offer when `book_input > 0` without `belief_price`. Pure-book (`declared_pool_input = 0`) unchanged. Verification: `make test-contracts` PASS; `cargo test -p cl8y-dex-tests hybrid_no_belief` PASS; frontend `swapMaxSpread.test.ts` PASS. MR link will be posted when created.
PlasticDigits commented 2026-06-05 09:59:19 +00:00 (Migrated from gitlab.com)

mentioned in merge request !785

mentioned in merge request !785
PlasticDigits commented 2026-06-05 09:59:21 +00:00 (Migrated from gitlab.com)
MR: https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/67
PlasticDigits commented 2026-06-05 10:54:19 +00:00 (Migrated from gitlab.com)

mentioned in commit 27e7a61846

mentioned in commit 27e7a6184680327a325ef93a83eb04855c0edce4
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-05 10:54:20 +00:00
ghost1 commented 2026-06-05 12:30:51 +00:00 (Migrated from gitlab.com)

mentioned in commit 845ea37c19

mentioned in commit 845ea37c1980db451537e47aece1ead056d0aceb
PlasticDigits commented 2026-06-05 12:31:16 +00:00 (Migrated from gitlab.com)

mentioned in merge request !801

mentioned in merge request !801
PlasticDigits commented 2026-06-05 12:31:16 +00:00 (Migrated from gitlab.com)

mentioned in issue #273

mentioned in issue #273
PlasticDigits commented 2026-06-05 13:44:36 +00:00 (Migrated from gitlab.com)

mentioned in issue #334

mentioned in issue #334
PlasticDigits commented 2026-06-05 14:18:31 +00:00 (Migrated from gitlab.com)

mentioned in merge request !819

mentioned in merge request !819
PlasticDigits commented 2026-06-09 06:50:53 +00:00 (Migrated from gitlab.com)

mentioned in commit 76723cf513

mentioned in commit 76723cf51316b9b2fa03e813a170bf8fd8363606
PlasticDigits commented 2026-06-09 06:53:30 +00:00 (Migrated from gitlab.com)

mentioned in merge request !847

mentioned in merge request !847
PlasticDigits commented 2026-06-09 06:53:44 +00:00 (Migrated from gitlab.com)

mentioned in issue #341

mentioned in issue #341
ghost1 commented 2026-06-09 07:07:54 +00:00 (Migrated from gitlab.com)

mentioned in merge request !849

mentioned in merge request !849
PlasticDigits commented 2026-06-13 07:09:04 +00:00 (Migrated from gitlab.com)

mentioned in issue #376

mentioned in issue #376
PlasticDigits commented 2026-06-25 13:12:38 +00:00 (Migrated from gitlab.com)

mentioned in issue #411

mentioned in issue #411
PlasticDigits commented 2026-06-25 14:12:57 +00:00 (Migrated from gitlab.com)

mentioned in issue #419

mentioned in issue #419
PlasticDigits commented 2026-08-30 05:24:16 +00:00 (Migrated from gitlab.com)

mentioned in issue #708

mentioned in issue #708
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#307
No description provided.