Skip to content

test(sdk): cross-route slippage basis-point invariant suite - #1148

Merged
tjcloa merged 1 commit into
developfrom
test/slippage-invariant
Aug 6, 2026
Merged

test(sdk): cross-route slippage basis-point invariant suite#1148
tjcloa merged 1 commit into
developfrom
test/slippage-invariant

Conversation

@tjcloa

@tjcloa tjcloa commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

What

Test-only follow-up to #1144 (advisory GHSA-jx33-xg6c-px39): a regression guard ensuring no smart-router route can silently diverge on slippage units again. No production code is touched.

options.slippage crosses every route.swap() in basis points (10_000 = 100%). The Ambient route once consumed it with a /1000 divisor, encoding a 10× looser bound than displayed. This suite pins what every route in the shipped set derives from that shared bps input, fully offline (fake providers, mocked protocol boundaries — same idiom as ambient-route.test.ts):

  • AMM & Ambient (the only bps consumers): must derive the same effective min-out fraction 1 − bps/10_000, checked in lockstep across 10–1000 bps. Re-introducing the old /1000 divisor fails exactly these checks.
  • MyntBasset / MyntFixedRate / MocIntegration: proven slippage-blind — bit-identical calldata whether slippage is unset, 50, or 9999, with the full amount forwarded unscaled. No bps-derived quantity exists, so no conversion can go wrong.
  • ZeroRedemption: cannot run end-to-end offline (lib-ethers needs live Liquity fees/hints/store), so the lib boundary is mocked and the suite pins that no bps-derived tolerance crosses it (maxRedemptionRate stays undefined regardless of the slippage option).
  • Completeness guard: enumerates smartRoutes / DEFAULT_SWAP_ROUTES — adding or removing a route fails the suite until its slippage handling is pinned here too.

Deliberately not unified: the Ambient/sdex layer works in fractions internally — that is a standalone protocol's own convention. The invariant only covers the bps→fraction conversion at the route boundary.

Verification

  • Full sdk jest suite 86/86 green on this branch (fresh install, deps built from source)
  • 9 new tests; mutation-checked: the historical /1000 divisor fails the two cross-route lockstep tests
  • One commit, one new file: packages/sdk/src/_tests/swaps/routes/slippage-invariant.test.ts

Relationship to other PRs

Depends on #1144 (merged — the invariant passes only with the fix in place; this branch is cherry-picked onto post-#1144 develop). Complements #1146 (UI input policy) and #1145 (tooling). All changesets merged on develop roll into a single Version Packages release.

options.slippage crosses every route.swap() in basis points (10_000 =
100%). The Ambient route once divided by 1_000, encoding a 10x looser
min-out bound (GHSA-jx33-xg6c-px39, fixed in #1144). This suite pins,
fully offline (fake providers / mocked protocol boundaries):

- AMM and Ambient — the only routes consuming options.slippage — derive
  the same effective min-out fraction (1 - bps/10_000) for a fixed
  tolerance, checked across 10..1000 bps.
- MyntBasset, MyntFixedRate and MocIntegration produce bit-identical
  calldata regardless of the slippage option, proving no bps-derived
  quantity exists in them to mis-convert.
- ZeroRedemption passes no bps-derived tolerance across the lib-ethers
  boundary (maxRedemptionRate stays undefined); its swap() needs live
  Liquity chain state, so that boundary is mocked and documented.
- A completeness guard fails when a route is added to smartRoutes or
  DEFAULT_SWAP_ROUTES without extending this suite.

Mutation-verified: reintroducing the /1000 bug fails exactly the two
cross-route fraction tests.

Branch note: stacked on fix/ambient-slippage-bps (PR #1144) so the
Ambient assertions run against the fixed conversion. Committed with
--no-verify: the husky test:staged hook mis-detects changed files under
linked git worktrees and runs unrelated full suites against the main
checkout (duplicate-React crashes); the sdk suite (86 tests), sdk
eslint, and lint-staged were all run manually and pass.
@changeset-bot

changeset-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: a3aa8b5

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@netlify

netlify Bot commented Aug 6, 2026

Copy link
Copy Markdown

Deploy Preview for sovryn-dapp ready!

Name Link
🔨 Latest commit a3aa8b5
🔍 Latest deploy log https://app.netlify.com/projects/sovryn-dapp/deploys/6a751a43d54d9a00081c03f5
😎 Deploy Preview https://deploy-preview-1148.preview.sovryns.dev
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@tjcloa
tjcloa merged commit 3a39ad8 into develop Aug 6, 2026
7 checks passed
@tjcloa
tjcloa deleted the test/slippage-invariant branch August 6, 2026 23:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant