Skip to content

refactor: stable slippage default#446

Open
Seranged wants to merge 5 commits into
developmentfrom
fix/stable-slippage-default
Open

refactor: stable slippage default#446
Seranged wants to merge 5 commits into
developmentfrom
fix/stable-slippage-default

Conversation

@Seranged

@Seranged Seranged commented May 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Stable-to-stable swap contexts use the 0.05% default slippage in the settings modal and transaction slippage state.
  • Slippage remains available from global user settings and per-flow slippage modals; high custom overrides apply within the pair-default bucket where they were set.
  • Overrides at or below the active default remain usable across contexts; stale, future-dated, invalid, or pair-default mismatched high overrides fall back to the active default.
  • Existing swap-slippage* storage keys hydrate into the explicit override shape for compatibility and are cleared after migration/reset so old values do not resurrect.
  • Embedded borrow/pay-with and position supply/withdraw swap flows pass their selected swap token pair into the slippage context.
  • The active custom slippage chip uses the active-chip text color for both Custom and the value.

Slippage Product Behavior

Swap slippage defaults follow the selected swap token pair:

  • Stable-to-stable pairs (for example, USDC -> RLUSD) default to 0.05%.
  • Other pairs (for example, wstETH -> WETH) default to 0.3%.

The slippage control is globally accessible from user settings and is also available inside swap flows. Custom slippage is stored as a user override with the pair-default bucket where it was set:

  • A high custom value such as 3% applies within the same default bucket where the user set it.
  • A 3% override set from global settings or a normal 0.3% pair applies to normal 0.3% pairs, but does not carry into stable-to-stable 0.05% pairs.
  • A 3% override set from a stable-to-stable 0.05% flow applies to stable-to-stable pairs, but does not carry into normal 0.3% pairs.
  • High custom overrides reset to the active pair default after 24 hours.
  • Lower-than-default overrides such as 0.01% can carry across pair types and do not expire just because 24 hours passed.

Existing persisted swap-slippage* values hydrate into the current override model. Values with a stored stable 0.05% bucket remain custom on stable pairs, while values without a stored bucket hydrate as normal 0.3% overrides and fall back to the stable 0.05% default on stable pairs.

Changes

  • Adds swap-slippage-override as the source of truth for persisted custom slippage.
  • Uses explicit JSON serialization for the nullable override object.
  • Computes effective slippage from the active swap pair default plus the stored override, without writing default/effective values back as custom overrides.
  • Clears migrated legacy keys when the override is persisted, reset, expired, invalid, or pair-default mismatched.
  • Keeps context-mismatched stable overrides from being cleared before the swap context is available.
  • Keeps lower-than-default overrides active even after 24h, while high overrides expire after 24h within their pair-default context.
  • Expands useSlippage coverage for stable/non-stable pairs, override context changes, existing storage hydration, stale/future timestamps, invalid overrides, and clearing custom values equal to the active default.

Test plan

  • npm run test:run -- tests/composables/useSlippage.test.ts tests/utils/swap-slippage-validation.test.ts (28 passing tests)
  • npm run lint
  • npm run typecheck
  • git diff --check
  • Local Nuxt slippage settings smoke on 127.0.0.1:3082 with deterministic injected swap contexts:
    • stable context defaults to 0.05%
    • non-stable context defaults to 0.3%
    • stable context keeps a stable-context 3% override and shows resets to 0.05% default after 24h
    • non-stable context keeps a generic-context 3% override and shows resets to 0.3% default after 24h
    • stable context rejects a generic-context 3% override
    • non-stable context rejects a stable-context 3% override
    • stable and non-stable contexts keep stale 0.01% lower-than-default overrides
    • stale and future-dated stable-context 3% overrides fall back to 0.05%
    • existing stable-context swap-slippage* keys hydrate to Custom 3%
    • existing generic-context swap-slippage* keys fall back to stable 0.05%
    • migrated legacy overrides are written to swap-slippage-override as JSON
    • active Custom 3% chip renders matching dark text for label and value

Summary by CodeRabbit

  • New Features

    • Slippage settings now adapt more accurately to the current swap context, including borrow, multiply, and collateral flows.
    • Added improved handling for custom slippage values, presets, and override state persistence.
  • Bug Fixes

    • Fixed slippage overrides resetting and expiring more reliably, including cleanup of outdated saved values.
    • Improved default slippage behavior for stablecoin-related swaps and prevented settings from applying in inactive form modes.

@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR refactors slippage handling into exported helper functions (stablecoin detection, context-based default slippage, override activation), introduces a SlippageOverride persistence model with legacy migration, gates useSlippage per form tab, updates borrow/collateral form symbol derivation, adjusts the settings UI, and adds new storage keys and tests.

Changes

Slippage logic extraction and component integration

Layer / File(s) Summary
Extract slippage helper functions and storage keys
composables/useSlippage.ts, entities/constants.ts
Adds exported helpers (isUsdStablecoin, isStablecoinSwapContext, getDefaultSlippageForContext, isSlippageOverrideActive), the SlippageOverride model, legacy-format parsing/migration, and new SLIPPAGE_OVERRIDE_STORAGE_KEY/SLIPPAGE_CONTEXT_DEFAULT_STORAGE_KEY constants.
Refactor useSlippage to use helpers
composables/useSlippage.ts
Recomputes defaultSlippage, isOverrideActive, and effectiveSlippage via the new helpers, adds an enabled gate skipping context writes, and clears persisted/legacy override state when inactive or mismatched.
Gate and parametrize useSlippage at call sites
composables/borrow/useBorrowForm.ts, composables/borrow/useMultiplyForm.ts, composables/position/useCollateralForm.ts
Enables slippage only for the active borrow/multiply tab and derives fromSymbol/toSymbol dynamically based on swap direction and form mode.
Update SlippageSettings component
components/entities/swap/SlippageSettings.vue
Adds isSlippageAboveDefault computed, updates the override-active template condition, sets slippageSelection to 'preset'/'custom' based on comparison to defaultSlippage on save, and tweaks custom chip value styling.
Add tests for slippage helpers
tests/composables/useSlippage.test.ts
Adds unit tests for helper classification/activation logic and integration tests for persisted override hydration, expiry, migration, and context-switch behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Form as Borrow/Multiply/Collateral Form
  participant useSlippage
  participant SwapContext
  participant Storage as localStorage

  Form->>useSlippage: call with enabled(), fromSymbol(), toSymbol()
  useSlippage->>useSlippage: check options.enabled()
  alt enabled is true
    useSlippage->>SwapContext: update shared swap context
    useSlippage->>useSlippage: getDefaultSlippageForContext(context)
    useSlippage->>Storage: read persisted SlippageOverride
    useSlippage->>useSlippage: isSlippageOverrideActive(override, now, defaultSlippage)
    alt override active
      useSlippage->>Form: effectiveSlippage = override value
    else override inactive/expired
      useSlippage->>Storage: clear override + legacy keys
      useSlippage->>Form: effectiveSlippage = defaultSlippage
    end
  else enabled is false
    useSlippage-->>SwapContext: skip context update
  end
Loading

Possibly related PRs

  • euler-xyz/euler-lite#81: Overlaps in SlippageSettings.vue around savePending and preset-vs-custom slippageSelection save behavior.
  • euler-xyz/euler-lite#107: Both modify SlippageSettings.vue threshold logic distinguishing custom vs preset/default slippage states.
  • euler-xyz/euler-lite#304: Both change SlippageSettings.vue messaging logic to use computed defaultSlippage instead of hardcoded values.

Suggested reviewers: dglowinski, kasperpawlowski

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and clearly reflects the main change: refactoring slippage defaults around stable swap contexts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/stable-slippage-default

Comment @coderabbitai help to get the list of available commands.

@railway-app

railway-app Bot commented May 14, 2026

Copy link
Copy Markdown

🚅 Deployed to the euler-lite-pr-446 environment in euler-lite

Service Status Web Updated (UTC)
dev-build ✅ Success (View Logs) Web Jul 3, 2026 at 12:47 pm

@railway-app railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-446 May 14, 2026 18:04 Destroyed
@Seranged Seranged force-pushed the fix/stable-slippage-default branch from 6e3d000 to b35f8da Compare May 14, 2026 18:17
@railway-app railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-446 May 14, 2026 18:17 Destroyed
@Seranged Seranged force-pushed the fix/stable-slippage-default branch from b35f8da to 57fe4ad Compare May 14, 2026 18:30
@railway-app railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-446 May 14, 2026 18:30 Destroyed
@Seranged Seranged force-pushed the fix/stable-slippage-default branch from 57fe4ad to f834784 Compare May 14, 2026 18:36
@railway-app railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-446 May 14, 2026 18:36 Destroyed
@Seranged Seranged marked this pull request as ready for review May 15, 2026 08:22

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/composables/useSlippage.test.ts (2)

21-29: ⚡ Quick win

Assert numeric defaults explicitly in this suite.

These expectations reuse DEFAULT_STABLECOIN_SLIPPAGE/DEFAULT_SLIPPAGE, so a constant regression can still pass here. Add at least one direct numeric assertion (e.g., 0.05 / 0.3) to pin product intent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/composables/useSlippage.test.ts` around lines 21 - 29, The test
currently only asserts against the constants DEFAULT_STABLECOIN_SLIPPAGE and
DEFAULT_SLIPPAGE which could hide regressions; update the cases in the it('uses
0.05% as the default for stablecoin swap pairs', ...) block (the const cases
array) to include at least one explicit numeric expectation alongside the
constants—e.g., replace or add an entry that expects 0.05 for stablecoin pairs
and one that expects 0.3 for non-stablecoin pairs (referencing
DEFAULT_STABLECOIN_SLIPPAGE and DEFAULT_SLIPPAGE for clarity), and update the
assertion that iterates over cases to check the numeric literal as well as the
constant.

36-50: ⚡ Quick win

Add an exact-expiry boundary test.

The suite checks +1ms past expiry, but not the exact setAt + SLIPPAGE_EXPIRY_MS boundary. Add one assertion to lock in intended > vs >= behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/composables/useSlippage.test.ts` around lines 36 - 50, Add an
exact-expiry boundary assertion for isSlippageOverrideActive: after computing
setAt and expiredNow in the test (using SLIPPAGE_EXPIRY_MS), call
isSlippageOverrideActive(DEFAULT_SLIPPAGE, setAt, setAt + SLIPPAGE_EXPIRY_MS,
DEFAULT_STABLECOIN_SLIPPAGE) and assert the expected boundary behavior (i.e.,
that the override is still active at exactly setAt + SLIPPAGE_EXPIRY_MS if your
logic treats expiry as strictly greater than the window, or assert false if your
logic treats it as >=); place this single assertion alongside the existing
expiredNow check so the test documents the exact >= vs > behavior for
SLIPPAGE_EXPIRY_MS and the isSlippageOverrideActive function.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/composables/useSlippage.test.ts`:
- Around line 21-29: The test currently only asserts against the constants
DEFAULT_STABLECOIN_SLIPPAGE and DEFAULT_SLIPPAGE which could hide regressions;
update the cases in the it('uses 0.05% as the default for stablecoin swap
pairs', ...) block (the const cases array) to include at least one explicit
numeric expectation alongside the constants—e.g., replace or add an entry that
expects 0.05 for stablecoin pairs and one that expects 0.3 for non-stablecoin
pairs (referencing DEFAULT_STABLECOIN_SLIPPAGE and DEFAULT_SLIPPAGE for
clarity), and update the assertion that iterates over cases to check the numeric
literal as well as the constant.
- Around line 36-50: Add an exact-expiry boundary assertion for
isSlippageOverrideActive: after computing setAt and expiredNow in the test
(using SLIPPAGE_EXPIRY_MS), call isSlippageOverrideActive(DEFAULT_SLIPPAGE,
setAt, setAt + SLIPPAGE_EXPIRY_MS, DEFAULT_STABLECOIN_SLIPPAGE) and assert the
expected boundary behavior (i.e., that the override is still active at exactly
setAt + SLIPPAGE_EXPIRY_MS if your logic treats expiry as strictly greater than
the window, or assert false if your logic treats it as >=); place this single
assertion alongside the existing expiredNow check so the test documents the
exact >= vs > behavior for SLIPPAGE_EXPIRY_MS and the isSlippageOverrideActive
function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: euler-xyz/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 67add0dd-255b-4be1-913b-45ef4bf1db26

📥 Commits

Reviewing files that changed from the base of the PR and between 7fcc092 and f834784.

📒 Files selected for processing (3)
  • components/entities/swap/SlippageSettings.vue
  • composables/useSlippage.ts
  • tests/composables/useSlippage.test.ts

@Seranged Seranged force-pushed the fix/stable-slippage-default branch from f834784 to 69bcf04 Compare May 18, 2026 11:16
@railway-app railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-446 May 18, 2026 11:16 Destroyed
@Seranged Seranged marked this pull request as draft May 18, 2026 11:18
@Seranged Seranged force-pushed the fix/stable-slippage-default branch from 69bcf04 to b9f3e6f Compare May 18, 2026 11:45
@railway-app railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-446 May 18, 2026 11:45 Destroyed
@Seranged Seranged force-pushed the fix/stable-slippage-default branch from b9f3e6f to bf2bff0 Compare May 18, 2026 11:54
@railway-app railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-446 May 18, 2026 11:56 Destroyed
@Seranged Seranged force-pushed the fix/stable-slippage-default branch from bf2bff0 to af8a974 Compare May 18, 2026 13:15
@railway-app railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-446 May 18, 2026 13:15 Destroyed
@Seranged Seranged force-pushed the fix/stable-slippage-default branch from af8a974 to 4ddd8e4 Compare May 18, 2026 13:27
@railway-app railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-446 May 18, 2026 13:27 Destroyed
@Seranged Seranged force-pushed the fix/stable-slippage-default branch from 4ddd8e4 to aa3d699 Compare May 18, 2026 15:45
@railway-app railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-446 May 18, 2026 15:45 Destroyed
@Seranged Seranged changed the title fix: stable slippage default refactor: stable slippage default May 18, 2026
Make slippage override expiry use the active pair default so stale generic overrides do not mask the stablecoin 0.05% default. Align the settings copy with the same pair-aware default and cover stable/non-stable pair cases.
@Seranged Seranged force-pushed the fix/stable-slippage-default branch from aa3d699 to 8ba875a Compare May 18, 2026 16:26
@railway-app railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-446 May 18, 2026 16:26 Destroyed
@Seranged Seranged marked this pull request as ready for review July 3, 2026 08:18
Keep newly saved slippage overrides active in the current tick, prevent stale per-instance storage refs from resurrecting cleared overrides, and reset the modal selection when a custom value is saved back to the active default. Add regressions for the smoke-tested edge cases and complete the repay test harness mocks used by the full suite.
@railway-app railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-446 July 3, 2026 09:43 Destroyed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
components/entities/swap/SlippageSettings.vue (1)

86-98: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated preset/default predicate risks drift across four call sites.

The condition "is this value a preset or the active default" is independently restated at Line 89, Line 95, Line 82, and Line 122. As shown by the bug above, restating it inconsistently already caused a mismatch. Consider extracting a single isPresetOrDefault(value) helper (closed over presetValues and defaultSlippage) and reusing it in all four places to prevent future divergence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/entities/swap/SlippageSettings.vue` around lines 86 - 98, The
preset/default check is duplicated across SlippageSettings.vue and has already
drifted, so centralize it into a single helper like isPresetOrDefault(value)
within the SlippageSettings setup logic. Update the existing watchEffect,
watch(slippage), and the other two call sites mentioned in the diff to use that
helper instead of repeating presetValues.includes(value) || value ===
defaultSlippage.value. Keep the helper closed over presetValues and
defaultSlippage so the selection state logic stays consistent in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/entities/swap/SlippageSettings.vue`:
- Around line 82-84: The save paths in SlippageSettings.vue are only treating
the exact default as a preset, so custom values that match any other preset stay
marked as custom. Update both onSaveCustom and savePending to use the same
preset-detection logic already used elsewhere in the file, namely checking
whether the saved value matches any entry in presetValues or equals
defaultSlippage.value. This should ensure slippageSelection is set to preset
whenever the saved value corresponds to an existing preset chip, not just the
current default.

---

Nitpick comments:
In `@components/entities/swap/SlippageSettings.vue`:
- Around line 86-98: The preset/default check is duplicated across
SlippageSettings.vue and has already drifted, so centralize it into a single
helper like isPresetOrDefault(value) within the SlippageSettings setup logic.
Update the existing watchEffect, watch(slippage), and the other two call sites
mentioned in the diff to use that helper instead of repeating
presetValues.includes(value) || value === defaultSlippage.value. Keep the helper
closed over presetValues and defaultSlippage so the selection state logic stays
consistent in one place.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: euler-xyz/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 560ae4b6-59bc-4e0b-957d-377142b1546f

📥 Commits

Reviewing files that changed from the base of the PR and between 8ba875a and 5837f6b.

📒 Files selected for processing (4)
  • components/entities/swap/SlippageSettings.vue
  • composables/useSlippage.ts
  • tests/composables/useSavingsRepay.test.ts
  • tests/composables/useSlippage.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/composables/useSlippage.test.ts
  • composables/useSlippage.ts

Comment thread components/entities/swap/SlippageSettings.vue Outdated

@LeonardEulerXYZ LeonardEulerXYZ left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed head 5837f6b70d87a7a4a72ef221157c7bbc2d33b930.

Verdict: request changes — I found one user-visible slippage override regression.

Validation run:

  • npm run test:run -- tests/composables/useSlippage.test.ts tests/utils/swap-slippage-validation.test.ts tests/composables/useSavingsRepay.test.ts — passed, 30 tests
  • npm run typecheck — passed
  • npm run lint — passed
  • npm run build — passed
  • Local headed Playwright smoke against an isolated route-level useSlippage({ fromSymbol, toSymbol }) + real SlippageSettings save flow — failed on the custom override invariant

Smoke coverage: browser interaction smoke, desktop/mobile harness over the slippage settings lifecycle. No screenshots posted because the finding is state/effective-value based rather than visual; the useful evidence is the failed assertion and state dump.

Scalability / maintainability hygiene: the new shared helper direction is good, and the route call sites now consistently pass swap context across swap/supply/withdraw/borrow/multiply/repay-style surfaces. The missed case is still a composition/lifecycle gap: tests cover isolated helper/composable behavior, but not the common mounted combination where a contextual route instance and the settings component's non-contextual instance both observe the shared override.

Bot feedback: CodeRabbit's preset-equivalence comment is a separate minor UI-state nit; I did not treat it as blocking. The blocker below is independently reproduced against the current head.

&& !hasCompleteSwapContext.value
) return

if (!isOverrideActive.value) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 875d690a.

Root cause was a per-instance now ref: the settings instance advanced its clock when saving the override, but the contextual route instance could still see the new setAt as future-dated and clear the shared override. now is now shared via useState("slippage-now"), and tests/composables/useSlippage.test.ts includes keeps a contextual override saved from a second settings instance to pin the route+settings lifecycle.

Also handled CodeRabbit’s preset-equivalence note by centralizing isPresetOrDefault() in SlippageSettings.vue and adding browser coverage for custom input matching preset 0.1%.

Validation after the fix:

  • npm run test:run — 113 files / 1094 tests passed
  • npm run typecheck — passed
  • npm run build — passed
  • browser slippage modal matrix — 19/19 passed

Seranged added 2 commits July 3, 2026 11:11
Resolve the development merge for the stable slippage PR, keep restored custom slippage selections visible, clear corrupt persisted slippage overrides, and give two SDK-heavy tests explicit timeout budgets.
Share slippage override timestamps across mounted instances, align custom saves with preset/default selection logic, and cover the reviewed lifecycle and boundary cases.
@railway-app railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-446 July 3, 2026 10:21 Destroyed
&& !hasCompleteSwapContext.value
) return

if (!isOverrideActive.value) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 875d690a.

Root cause was a per-instance now ref: the settings instance advanced its clock when saving the override, but the contextual route instance could still see the new setAt as future-dated and clear the shared override. now is now shared via useState("slippage-now"), and tests/composables/useSlippage.test.ts includes keeps a contextual override saved from a second settings instance to pin the route+settings lifecycle.

Also handled CodeRabbit’s preset-equivalence note by centralizing isPresetOrDefault() in SlippageSettings.vue and adding browser coverage for custom input matching preset 0.1%.

Validation after the fix:

  • npm run test:run — 113 files / 1094 tests passed
  • npm run typecheck — passed
  • npm run build — passed
  • browser slippage modal matrix — 19/19 passed

@Seranged Seranged requested a review from LeonardEulerXYZ July 3, 2026 10:26

@LeonardEulerXYZ LeonardEulerXYZ left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed head 875d690ac0555771701b509a66d9a1c21526c543.

Verdict: request changes — the original useSlippage route/settings lifecycle blocker looks fixed, but the new pair-sensitive default exposes a remaining route-integration bug in the refinance page.

Validation run:

  • npm run test:run -- tests/composables/useSlippage.test.ts tests/composables/useEulerAccount.test.ts tests/composables/useEulerSdk.test.ts — passed, 30 tests.
  • npm run typecheck — passed.
  • Direct code trace of the refinance slippage context against the actual collateral/debt quote request assets.

Prior Leonard blocker status: I rechecked the current useSlippage implementation. The shared useState("slippage-now"), the incomplete-context guard, and the new keeps a contextual override saved from a second settings instance test cover the earlier route+settings override-erasure failure, so I am not reposting that issue.

Blocking finding:

Refinance debt quotes can choose the wrong default slippage pair

pages/position/[number]/borrow/swap.vue still provides one route-level slippage context as:

fromSymbol: () => sourceCollateralVault.value?.asset.symbol || sourceDebtVault.value?.asset.symbol,
toSymbol: () => targetCollateralVault.value?.asset.symbol || targetDebtVault.value?.asset.symbol,

but the debt quote request actually swaps the target debt asset into the source debt asset:

tokenIn: targetDebtVault.value.asset.address,
tokenOut: sourceDebtVault.value.asset.address,
slippage: slippage.value,

Now that this PR makes the default depend on the pair, that mismatch is user-visible. For a debt-only refinance:

  • current collateral USDC, current debt WETH, target debt USDC ⇒ context becomes USDC -> USDC, so the default is 0.05%, while the actual debt quote is USDC -> WETH and should use the normal 0.3% default.
  • current collateral WETH, current debt USDC, target debt USDT ⇒ context becomes WETH -> USDT, so the default is 0.3%, while the actual debt quote is stable-to-stable USDT -> USDC and should use 0.05%.

The same single-context problem appears when both collateral and debt legs are active: one displayed/default slippage value is reused for both quote requests even if one leg is stable-stable and the other is not.

Please derive the default from the actual active swap leg(s) on this route. A safe rule would be: use the stablecoin default only when every active quote leg that needs a swap is stable-to-stable; otherwise keep the normal 0.3% default, or split the route into leg-specific slippage values. The invariant is that the slippage default shown/sent for a quote must be based on the assets in that quote, not on whichever collateral/debt symbol happens to win the current fallback order.

Browser smoke: not run — this finding is from code-path validation of the quote request wiring; focused composable tests and typecheck pass.

Derive refinance slippage context from the active quote legs so debt-only and mixed-leg flows use stable defaults only when every active swap is stable-to-stable.
@Seranged

Seranged commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Addressed Leonard’s refinance slippage blocker in 5e939c81.

What changed:

  • The refinance page now builds slippage context from the actual active quote legs.
  • Collateral leg uses source collateral asset -> target collateral asset.
  • Debt leg uses target debt asset -> source debt asset, matching the quote request.
  • Mixed collateral+debt flows use the stablecoin default only when every active swap leg is stable-to-stable; otherwise they use the normal 0.3% default.

Validation:

  • npm run test:run -- tests/utils/refinance-slippage.test.ts tests/composables/useSlippage.test.ts
  • npx eslint pages/position/[number]/borrow/swap.vue utils/refinance-slippage.ts tests/utils/refinance-slippage.test.ts
  • npm run test:run -- tests/composables/useSlippage.test.ts tests/composables/useEulerAccount.test.ts tests/composables/useEulerSdk.test.ts tests/utils/refinance-slippage.test.ts
  • npm run test:run (114 files / 1097 tests)
  • npm run typecheck
  • npm run build
  • Slippage modal browser smoke matrix: 19/19 passed

@LeonardEulerXYZ LeonardEulerXYZ left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed head 5e939c8108e5c823187121693d0f5da6c44aae5a.

Verdict: comment — no blocking issues found in this pass. I am not approving automatically.

What I checked:

  • Re-read the changed slippage persistence/defaulting path, SlippageSettings, borrow/multiply/collateral swap call sites, and the refinance-page slippage context fix.
  • Reconstructed the prior refinance invariant: the slippage default must be derived from the actual active quote leg(s), using the stable default only when every active swap leg is stable-to-stable. The new getRefinanceSlippageContext() helper and its tests now cover the debt-quote direction and mixed-leg fallback correctly.
  • Scalability / maintainability hygiene pass: searched the sibling useSlippage() consumers across lend, withdraw, repay, multiply, collateral swap, generic swap-page logic, and refinance. The patch is no longer a one-off page fallback; the refinance multi-leg decision is isolated in a small tested utility, and borrow/multiply tab coexistence is guarded with enabled. I did not find a sibling surface still carrying the same pair-default bug.
  • Bot/reviewer feedback pass: CodeRabbit’s older preset-equivalence nit is addressed by the shared isPresetOrDefault() checks in the modal save paths. I did not find a material active bot finding to amplify.

Validation run:

  • npm run test:run -- tests/composables/useSlippage.test.ts tests/utils/refinance-slippage.test.ts tests/composables/useEulerAccount.test.ts tests/composables/useEulerSdk.test.ts — passed, 33 tests.
  • npm run typecheck — passed.
  • npx eslint <changed files> — passed.
  • npm run build — passed.
  • npm run lint repo-wide is still blocked by existing errors outside the PR diff; I did not treat those as PR-specific.
  • Light headed Playwright route smoke against the Railway preview: / and /borrow?network=1 loaded as the real app on desktop and mobile viewports, with no page/console errors observed.

Smoke coverage: browser route smoke + mobile route smoke only. I did not run wallet/signing coverage, and I did not capture/post screenshots because this pass did not produce a meaningful visual before/after artifact for the slippage-default logic.

Previous Leonard blockers: resolved in the current head based on code trace plus focused regression coverage.

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.

2 participants