Skip to content

[HIGH] Systemic NaN propagation — zero Number.isFinite guards in display layer #2313

Description

@v1ktorrr0x

Severity: High

Source

Cross-cutting — 9 files across Earn components

Description

There are zero Number.isFinite() guards anywhere in the Earn display pipeline. A single corrupt upstream value (RPC returning bad data, API returning malformed numbers) cascades to every display boundary, rendering literal "NaN", "NaN%", or "$NaN" in the DOM.

formatCompact (defined independently in 4 files) has the same vulnerable pattern everywhere:

function formatCompact(n: number): string {
  if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)}M`;
  if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
  return n.toFixed(2); // NaN falls through all guards -> "NaN"
}

Affected files

File What renders
components/earn/VaultCard.tsx:54,63,67,71,89 "NaN%", "$NaN" on .toFixed()
components/earn/LpPositionDashboard.tsx:134,137,144 "NaN" share value, APY
components/earn/OiCapMeter.tsx:57,67 width: NaN% (bar invisible)
components/earn/EarnHeader.tsx:53-94 AnimatedNumber receives NaN
components/earn/InsuranceFundDisplay.tsx:69-75,123 Same
app/earn/[slab]/page.tsx:432 formatCompact(NaN) renders "NaN"

Fix

  1. Add a single shared formatCompact in lib/format.ts with Number.isFinite(n) guard, returning "—" for invalid values
  2. Replace all 4 independent copies with imports
  3. Add Number.isFinite before every .toFixed() call and AnimatedNumber value prop in the Earn component tree

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions