Skip to content

fix(pegged-swap): correct PeggedPrice lt-quote conversions for mixed-decimal pairs - #83

Merged
krboktv merged 1 commit into
masterfrom
cursor/fix-pegged-price-mixed-decimals-247d
Jul 16, 2026
Merged

fix(pegged-swap): correct PeggedPrice lt-quote conversions for mixed-decimal pairs#83
krboktv merged 1 commit into
masterfrom
cursor/fix-pegged-price-mixed-decimals-247d

Conversation

@krboktv

@krboktv krboktv commented Jul 16, 2026

Copy link
Copy Markdown
Member

Change Summary

What does this PR change?

Fixes PeggedPrice (swap-vm SDK) producing wrong values when a mixed-decimals pegged pair is quoted in the lower-address (lt) token.

The class mixed two definitions of its internal marginal rate: the canonical raw gt-per-lt rate in 1e18 fixed-point (used by fromReserves, toGtPerLtE18(), PeggedSwapCalculator.computeFixedAllocation, and the gt-quote branches of toHuman/fromHuman) and a human-unit rate (used only by the lt-quote branches of toHuman and fromHuman). The two definitions coincide only when both tokens have equal decimals; for mixed-decimals pairs (e.g. a 6/18 USDC/DAI-style pool) lt-quoted prices were off by 10^|gtDecimals - ltDecimals|. Example: a 6/18 pair with equal-value reserves returned toHuman(gt) === '1' (correct) but toHuman(lt) === '0' instead of '1'.

This PR makes raw gt-per-lt e18 the single canonical meaning and fixes the two lt-quote branches to invert it with the proper raw→human decimals adjustment:

  • toHuman, lt-quote branch: scaled value is now 10^(18 + gtDecimals) / marginalE18 (was 10^(gtDecimals + 18 + ltDecimals) / (marginalE18 * 10^gtDecimals), which dropped the 10^(gtDecimals - ltDecimals) adjustment).
  • fromHuman, lt-quote branch: marginalE18 is now 10^(18 + gtDecimals) / parsed (was the matching buggy inverse, which made lt-quote round-trips self-consistently mask the bug).

fromReserves, fromGtPerLtE18/toGtPerLtE18 (the internal gtPerLtRaw storage round-trips consistently), the gt-quote branches, and all equal-decimals behavior are unchanged. PeggedSwapCalculator and the e2e specs consume toGtPerLtE18()/gt-quote fromHuman only, so they are unaffected (verified by running their tests).

Related Issue/Ticket:

No tracker ticket. Found while implementing opening-price support in 1inch/aqua-api PR #149, which currently works around this bug by converting toGtPerLtE18() to a sqrt price and rendering through instructions.concentrate.Price; that workaround can be simplified once this fix ships.

Testing & Verification

How was this tested?

  • Unit tests
  • Integration tests
  • Manual testing (describe steps)
  • Verified on staging

Added regression tests in pegged-price.test.ts:

  • Mixed-decimals (6/18 and 18/6) fromReserves(...).toHuman() returns the true price in both quote directions (equal-value reserves, price exactly '1' both ways; previously the lt quote returned '0').
  • Cross-direction consistency: fromHuman with lt quote read back with gt quote gives the exact inverse ('2000''0.0005'), and vice versa (previously off by 1e12).
  • An equal-decimals (18/18) case demonstrating no behavior change there.

Existing lt-quote round-trip tests were kept as-is — they passed before only because the two buggy branches were self-consistent inverses; they still pass now that both branches are correct.

Verification commands (all green): pnpm --filter @1inch/swap-vm-sdk test (44 files, 600 tests), pnpm test, pnpm lint, pnpm lint:types (after pnpm build:contracts + pnpm build). Also manually reproduced the numeric repro from the report against the fixed build.

Risk Assessment

Risk Level:

  • Low - Minor changes, no operational impact
  • Medium - Moderate changes, limited impact, standard rollback available
  • High - Significant changes, potential operational impact, complex rollback

Risks & Impact

This is a behavioral change for any consumer that quotes a mixed-decimals pegged price in the lower-address token via toHuman/fromHuman: those calls previously returned values off by 10^|gtDecimals - ltDecimals| and now return correct ones. Consumers that compensated for the bug downstream (like the aqua-api workaround above) should drop their compensation when upgrading. Equal-decimals pairs, fromReserves, toGtPerLtE18(), gt-quote paths, and PeggedSwapCalculator are unaffected, and JSON round-trips are unchanged. Rollback is a standard revert / version pin.

…decimal pairs

PeggedPrice mixed two definitions of its marginal rate: the canonical
raw gt-per-lt e18 (used by fromReserves, toGtPerLtE18 and the gt-quote
branches) and a human-unit rate (used by the lt-quote branches of
toHuman and fromHuman). The definitions only coincide when both tokens
have equal decimals, so mixed-decimal pairs (e.g. 6/18) quoted in the
lower-address token were off by 10^|dGt-dLt|.

Fix the two lt-quote branches to invert the raw rate with the proper
raw->human decimals adjustment: scaled/marginal = 10^(18 + gtDecimals)
divided by the counterpart, instead of 10^(gtDecimals + 18 + ltDecimals)
over (counterpart * 10^gtDecimals). Equal-decimals pairs are unaffected.
@krboktv
krboktv marked this pull request as ready for review July 16, 2026 08:12
@krboktv
krboktv merged commit 9a4abbe into master Jul 16, 2026
1 check passed
@krboktv
krboktv deleted the cursor/fix-pegged-price-mixed-decimals-247d branch July 16, 2026 08:12
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