fix(share-pnl): pin leverage to lease-open snapshot#165
Merged
Conversation
The share-pnl card computed leverage as `(downPayment + totalDebt) / downPayment` with downPayment in USD and totalDebt in LPN units — dimensionally correct only for longs (LPN = USDC ≈ $1). For shorts the LPN is the volatile asset (OSMO, WBTC, etc.) so totalDebt was a raw asset quantity, producing absurd leverages like x12.3 on a real 1.7x position. Plumb `LS_loan_amnt_stable` through from ETL (loan principal in stable USD at open) and compute `leverageAtOpen = (downPayment + loanAtOpen) / downPayment` once in calculateDisplayData. The share-pnl dialog prefers this frozen value so the displayed leverage stays pinned to what the user actually opened at — 2.5x at the protocol max stays 2.5x for the life of the lease, regardless of accruing interest or manual repayments. Falls back to a live computation using totalDebtUsd (instead of totalDebt) when the lease pre-dates the new field, fixing the dimensional bug even in the fallback path.
Mirrors the new field added to EtlLeaseInfo in the previous commit; without it the `make_etl_with_collateral` helper fails to compile in the test target.
Adds the new `LeaseEtlData.loan_amount_stable` field to the committed OpenAPI spec snapshot. Generated via `UPDATE_OPENAPI_SNAPSHOT=1 cargo test openapi`.
A real BTC short surfaced a denomination quirk: LS_loan_amnt_stable on ETL /ls-opening is scaled by the LPN's native decimals (1e6 for OSMO, 1e8 for ALL_BTC, etc.), not by stable USDC decimals. The previous `new Dec(loan_amount_stable, 6)` inflated the loan 100x on BTC shorts and produced x76 leverage on a position actually opened at x1.75. Pass `lpnDecimals` (already in scope from the surrounding calculateDisplayData) to the Dec constructor so the scaling matches the LPN's native decimals. OSMO continues to work because its LPN decimals coincide with stable decimals.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fix the share-pnl card's leverage calculation, which produced wildly inflated values for short positions (e.g. x12.3 displayed on a real 1.7x position) due to a dimensional mismatch between USD and LPN units. Pin the displayed value to the lease-open snapshot so it matches what the user actually opened at and doesn't drift with interest accrual or manual repayments.
Changes
Backend
backend/src/external/etl.rs— addsloan_amount_stablefield onEtlLeaseInfo(renames from ETL'sLS_loan_amnt_stable, the loan principal in stable USD at lease open).backend/src/handlers/leases.rs— addsloan_amount_stabletoLeaseEtlDataand propagates it through the etl_info mapping.Frontend
src/common/api/types/leases.ts— addsloan_amount_stable?: stringtoLeaseEtlData.src/common/utils/LeaseCalculator.ts— addsleverageAtOpen: Dec | nulltoLeaseDisplayData;calculateDisplayDatacomputes(downPayment + loanAtOpen) / downPaymentfrom the frozen lease-open snapshot. Both fields are in stable USDC decimals (6), so the units line up across longs and shorts.src/modules/leases/components/single-lease/SharePnLDialog.vue—leverageMultipleprefersleaseDisplayData.leverageAtOpen. Falls back to a live computation usingtotalDebtUsd(instead of the previoustotalDebt) when ETL hasn't exposedloan_amount_stableyet; this also fixes the dimensional bug in the fallback path for older leases.Root cause
The previous
leverageMultipleformula was(downPayment + totalDebt) / downPayment.downPaymentcame fromparseEtlDataand is denominated in USD (LS_cltr_amnt_stable: asset_micro_units × price, divided by collateral decimals).totalDebtcame fromcalculateDebtand is denominated in LPN units (new Dec(lease.debt.principal, lpnDecimals)).For longs the LPN is USDC ≈ $1, so the LPN-quantity ≈ USD value and the formula accidentally worked. For shorts the LPN is the volatile asset — on the OSMO short we verified,
totalDebt ≈ 1072 OSMOwas added todownPayment ≈ \$40.10USD, producing a wildly inflated leverage.Verification
cargo checkandcargo clippy -- -D warningsonbackend/passed clean.npx vue-tsc --noEmitandnpx eslinton the changed files passed clean.npx vitest run src/common/utils/LeaseCalculator.test.ts— all 73 existing tests still pass (no behavioral change incalculateDebt,parseEtlData, etc.;leverageAtOpenis a new additive field).nolus1y6j8c6u8z02f7jfe3wt3nyn7de8cjuk2ak79w5fxatzua8r62zwqufcg25(the OSMO short used to track this down):LS_cltr_amnt_stable = $40.10,LS_loan_amnt_stable = $29.57, soleverageAtOpen = (40.10 + 29.57) / 40.10 = 1.74— matches the protocol-true initial leverage and replaces the buggy x12.3 the card previously displayed.Follow-ups
The dashboard / single-lease detail pages don't show leverage on the card so they're unaffected, but the new
leverageAtOpenfield is available onLeaseDisplayDataif you ever want to surface a frozen-leverage badge elsewhere in the UI.