Skip to content

fix(sns): read the swap participant count only from the certified state - #8049

Open
yhabib wants to merge 5 commits into
mainfrom
fix/sns-swap-metrics-certified
Open

fix(sns): read the swap participant count only from the certified state#8049
yhabib wants to merge 5 commits into
mainfrom
fix/sns-swap-metrics-certified

Conversation

@yhabib

@yhabib yhabib commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Motivation

The SNS project detail page is where a user decides to join a sale. When the derived swap state carried no participant count, the page fetched it from https://<swap>.raw.icp0.io/metrics, a raw domain with no response certification. A single replica on the swap subnet could answer that request with a forged count and mislead a viewer about how close the sale is to succeeding.

Every live SNS swap canister already reports direct_participant_count through the certified get_derived_state call, so the raw fallback had no real target left. Only one aborted swap (SONIC) still lacked the field, and its swap canister holds no wasm, so the raw fetch answered nothing there too.

Changes

  • Removed the raw /metrics fetch and its api, service, and store modules.
  • Changed swapSaleBuyerCount to read the count only from the certified direct_participant_count field.
  • Removed the raw-metrics watcher branch and its imports from ProjectDetail.svelte and ProjectCommitment.svelte.
  • Added a changelog entry under Security.
  • Added an e2e spec that participates in a sale and asserts no request ever goes to the raw metrics domain.

Tests

  • Added a regression test in ProjectDetail.spec.ts that fails on main (the raw fetch fires) and passes on this branch.
  • Added fail-closed tests in ProjectCommitment.spec.ts and sns-swap.utils.spec.ts: an empty direct_participant_count shows no count and no "minimum reached" banner, instead of a false zero.
  • Added an e2e spec, sns-swap-participant-count.spec.ts, that records every network request, participates in a live sale, and checks the raw domain is never called while the participant count still rises by one.
  • npm run check, CI=true npm run test, and ./scripts/check-relative-imports all pass.

Todos

The project detail page read the participant count from
`https://<swap>.raw.icp0.io/metrics` when `get_derived_state` returned no
`direct_participant_count`. The raw gateway skips response certification, so
one replica could answer with any count.

Delete the raw metrics path. `direct_participant_count` from the certified
`get_derived_state` call is now the only source of the count.
The spec opens a sale that accepts participation, reads the participant
count, participates, and checks that the count rises by one. It also records
every network request and checks that no request goes to a raw metrics URL.
…spec

step() is async; a call without await lets Playwright run the next step
before the report closes the previous one.
@yhabib
yhabib requested a review from a team as a code owner September 4, 2026 20:32
@yhabib
yhabib requested a lite review from Copilot September 4, 2026 20:32
@zeropath-ai

zeropath-ai Bot commented Sep 4, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to 4ce6e46.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► frontend/src/lib/utils/sns-swap.utils.ts
    Improve swapSaleBuyerCount implementation to derive count from derived state
► frontend/src/lib/utils/sns.utils.ts
    Remove parsing of raw metrics and simplify hasBuyersCount logic
Bug Fix ► frontend/src/lib/components/project-detail/ProjectCommitment.svelte
    Remove direct dependency on sns-swap-metrics store
► frontend/src/lib/pages/ProjectDetail.svelte
    Stop importing loadSnsSwapMetrics and hasBuyersCount from derived state to reduce raw metrics usage
Refactor ► frontend/src/lib/stores/sns-swap-metrics.store.ts
    Delete sns-swap-metrics store (removal of store usage)
Refactor ► frontend/src/lib/services/sns-swap-metrics.services.ts
    Delete sns-swap-metrics service (removal of loadSnsSwapMetrics)
Refactor ► frontend/src/lib/api/sns-swap-metrics.api.ts
    Delete API for querying raw swap metrics (removed)
Refactor ► frontend/src/tests/lib/stores/sns-swap-metrics.store.spec.ts
    Delete tests for sns-swap-metrics.store
Refactor ► frontend/src/tests/lib/services/sns-swap-metrics.services.spec.ts
    Delete tests for sns-swap-metrics services
Refactor ► frontend/src/tests/lib/utils/sns-swap.utils.spec.ts
    Update tests to reflect new swapSaleBuyerCount behavior (remove parseSnsSwapSaleBuyerCount usage)
Bug Fix ► frontend/src/tests/lib/pages/ProjectDetail.spec.ts
    Remove mocking of sns-swap-metrics.api and related raw metrics usage; adjust expectations accordingly
Enhancement ► frontend/src/tests/e2e/sns-swap-participant-count.spec.ts
    Add e2e test to verify participant count is derived from derived state and no raw metrics fetch occurs
Enhancement ► frontend/src/tests/lib/components/project-detail/ProjectCommitment.spec.ts
    Adjust tests to align with derived state buying count and participants presence logic
► frontend/src/tests/page-objects/ProjectCommitment.page-object.ts
    Add hasParticipantsCount helper method

Copilot AI 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.

🟡 Changes recommended

A newly added unit-test assertion relies on global.fetch being a Vitest mock but it is not stubbed in the test setup, which can cause the test to fail/throw before validating the behavior.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR removes the uncertified raw /metrics fallback for SNS swap participant counts and ensures the UI only displays the count when it is present in the certified get_derived_state response, closing a spoofing vector from the raw gateway.

Changes:

  • Removed the raw https://<swap>.raw.icp0.io/metrics fetch path (API/service/store/util parsing) and associated watcher branches.
  • Updated swap participant-count derivation to read only direct_participant_count from the certified derived state, and fail-closed when missing.
  • Added/updated tests (unit + e2e) and added a Security changelog entry to prevent regressions.
File summaries
File Description
frontend/src/lib/utils/sns-swap.utils.ts Drops raw-metrics fallback; reads participant count only from certified derived state.
frontend/src/lib/pages/ProjectDetail.svelte Removes raw-metrics watcher/fetch branch and related imports.
frontend/src/lib/components/project-detail/ProjectCommitment.svelte Removes metrics-store dependency; renders participant count only when certified field exists.
frontend/src/lib/utils/sns.utils.ts Removes raw-metrics parsing helper used by the deleted fallback path.
frontend/src/lib/api/sns-swap-metrics.api.ts Deleted raw /metrics API client.
frontend/src/lib/services/sns-swap-metrics.services.ts Deleted service that fetched/parses/stores raw metrics.
frontend/src/lib/stores/sns-swap-metrics.store.ts Deleted store that cached raw metrics per SNS root canister.
frontend/src/tests/lib/pages/ProjectDetail.spec.ts Updates expectations to assert no raw-domain fetch is performed.
frontend/src/tests/lib/components/project-detail/ProjectCommitment.spec.ts Adds fail-closed assertions when derived participant count is missing.
frontend/src/tests/lib/utils/sns-swap.utils.spec.ts Updates utils tests to cover certified-only behavior and missing-field semantics.
frontend/src/tests/lib/utils/sns.utils.spec.ts Removes tests for deleted raw-metrics parsing helper.
frontend/src/tests/lib/api/sns-swap-metrics.api.spec.ts Deleted tests for removed raw-metrics API.
frontend/src/tests/lib/services/sns-swap-metrics.services.spec.ts Deleted tests for removed raw-metrics service.
frontend/src/tests/lib/stores/sns-swap-metrics.store.spec.ts Deleted tests for removed raw-metrics store.
frontend/src/tests/page-objects/ProjectCommitment.page-object.ts Adds a helper to assert the participant count row exists.
frontend/src/tests/e2e/sns-swap-participant-count.spec.ts New e2e regression spec ensuring no raw metrics requests occur and count increases after participating.
CHANGELOG-Nns-Dapp-unreleased.md Documents the security fix under the Security section.
Review details
  • Files reviewed: 17/17 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread frontend/src/tests/lib/pages/ProjectDetail.spec.ts

Copilot AI 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.

🟡 Changes recommended

A page-object helper used to assert absence of the “minimum reached” banner can still time out/throw in Playwright when the element is missing, so it should be adjusted to return null safely and consistently.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 17/17 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread frontend/src/tests/page-objects/ProjectCommitment.page-object.ts
getGoalReachedMessage called getText, which times out in the Playwright
backend when the element is not present. Check isPresent first and
return null, so the helper behaves the same in Jest and Playwright.

Copilot AI 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.

🔵 Needs a closer look

The new e2e test can be flaky under parallel Playwright workers because other tests also participate in the same sale, so the participant count may increase by more than one.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

frontend/src/tests/e2e/sns-swap-participant-count.spec.ts:78

  • This e2e assertion expects the participant count to increase by exactly 1 after participating. Because Playwright is configured with multiple workers (frontend/playwright.config.ts sets workers: 6) and other e2e specs (e.g. sns-participation.spec.ts) also participate in the same sale, this can be flaky when another worker participates concurrently and the count jumps by >1. Prefer asserting the count increases by at least 1 (or make the suite serial / isolate the sale).
  • Files reviewed: 17/17 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Another e2e file participates in the same first upcoming sale, so a
worker running both files at once can raise the count by more than
one. Check for at least one more, not exactly one more.
@yhabib

yhabib commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

Fixed: the e2e count assertion now checks for at least one more participant (toBeGreaterThanOrEqual), not exactly one more. Another spec (sns-participation.spec.ts) participates in the same first upcoming sale and can run in a different worker at the same time.

Copilot AI 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.

🟢 Approved

The raw, uncertified metrics pathway is fully removed (code + imports + tests), and new unit/e2e coverage verifies the app no longer makes raw metrics requests while failing closed when the certified field is absent.

Review details
  • Files reviewed: 17/17 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

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