Skip to content

fix(app): reject future-dated Chainlink rounds in feedUsd - #29

Open
Ayush7614 wants to merge 2 commits into
Gitlawb:mainfrom
Ayush7614:fix/feed-future-round-guard
Open

fix(app): reject future-dated Chainlink rounds in feedUsd#29
Ayush7614 wants to merge 2 commits into
Gitlawb:mainfrom
Ayush7614:fix/feed-future-round-guard

Conversation

@Ayush7614

@Ayush7614 Ayush7614 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

feedUsd accepted updatedAt in the future: nowS - updatedAt goes negative and never exceeds maxAgeS, so a skewed clock or bad RPC round was served as a live price. feedEthUsd delegates to the same helper, so ETH/USD had the same hole.

Before (baseStocks.ts): if (!(r.updatedAt > 0) || nowS - r.updatedAt > maxAgeS) — future passes.
After: if (!(r.updatedAt > 0) || r.updatedAt > nowS || nowS - r.updatedAt > maxAgeS) — future is null, stocks and ETH both covered. Adds regression cases in baseStocks.test.ts and ethPrice.test.ts.

Verified locally on upstream/main base:

  • npm run lint: pass
  • npx tsc --noEmit -p .: pass
  • targeted baseStocks + ethPrice suites: 21 pass, 0 fail
  • full unit suite: 323 pass, 0 fail
  • npm run build: pass

Summary by CodeRabbit

  • Bug Fixes

    • Rejects price feed readings with timestamps in the future.
    • Prevents future-dated ETH/USD and other asset prices from being used.
    • Correctly accepts valid fallback readings when delays mean they are no longer future-dated.
    • Records refresh timing after live price resolution for more accurate validation.
  • Tests

    • Adds coverage for minor and substantial future timestamp discrepancies.
    • Verifies fallback behavior after delayed price feed reads.

feedUsd accepted updatedAt in the future (nowS - updatedAt negative
never exceeds maxAgeS), so a skewed clock or bad RPC round served as a
price. feedEthUsd delegates to the same helper, so ETH/USD had the same
hole. Reject r.updatedAt > nowS and cover it in baseStocks + ethPrice
tests.
@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 83a3ab9d-b444-49d9-80e4-261d597c8c48

📥 Commits

Reviewing files that changed from the base of the PR and between 0c5e80e and fc7b386.

📒 Files selected for processing (2)
  • app/src/lib/launchpad/ethPrice.test.ts
  • app/src/lib/launchpad/ethPrice.ts

Limit details: You’ve used the included review currently available.


📝 Walkthrough

Walkthrough

The change rejects future-dated oracle readings in feedUsd and feedEthUsd. ETH fallback timing now samples the clock after feed reads and records refresh timestamps after source resolution.

Changes

Oracle timestamp validation

Layer / File(s) Summary
USD stock feed validation
app/src/lib/launchpad/baseStocks.ts, app/src/lib/launchpad/baseStocks.test.ts
feedUsd returns null for future-dated readings. Tests cover near-future and substantially future timestamps.
ETH/USD feed validation
app/src/lib/launchpad/ethPrice.ts, app/src/lib/launchpad/ethPrice.test.ts
feedEthUsd rejects future-dated Chainlink rounds. Tests cover this validation.
ETH fallback timing
app/src/lib/launchpad/ethPrice.ts, app/src/lib/launchpad/ethPrice.test.ts
Chainlink validation samples the clock after the feed read. Refresh timestamps are recorded after source resolution. Tests cover delayed fallback behavior.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested reviewers: kevincodex1, vasanthdev2004

Merge Risk: ⚪ Minimal · up to fc7b3

Future-dated oracle readings are rejected while delayed fallback rounds remain valid when read. No actionable merge risk remains.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary change: rejecting future-dated Chainlink rounds in feedUsd. It is concise and specific.
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.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Usage-based review receipt

Note

This review was completed with usage-based billing: files reviewed beyond your plan's included limits are billed at $0.25/file. View usage-based billing.


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

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The future-date guard is useful, but its ETH fallback caller currently supplies a clock captured before the network requests. That makes this reject valid rounds published while the request is running. Please update that integration before merging.

I ran the 21 stock/ETH tests successfully, then reproduced the regression with an advancing-clock test: the existing implementation returns 2441.34, while this head returns null for the same valid round.

export function feedUsd(r: { answer: bigint; updatedAt: number } | null, nowS: number, feedDecimals = 8, maxAgeS = BASE_STOCK_MAX_FEED_AGE_S): number | null {
if (!r || r.answer <= 0n) return null;
if (!(r.updatedAt > 0) || nowS - r.updatedAt > maxAgeS) return null;
if (!(r.updatedAt > 0) || r.updatedAt > nowS || nowS - r.updatedAt > maxAgeS) return null;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Compare the feed timestamp with the clock after the read completes. ethUsd() captures t before awaiting Coinbase, then passes it through refresh() and fromChainlink(). If Coinbase fails after 5 seconds and Chainlink returns a round published 3 seconds after the request started, this guard rejects it even though it is already 2 seconds old. I reproduced a cold fallback returning null instead of 2441.34; the previous implementation accepts it. Pass the current/injected clock through the fallback and sample it after await feedFn(), then add an integration test with elapsed time during the failed Coinbase request.

…are not rejected as future (PR Gitlawb#29)

- fromChainlink takes injected now() and samples after await feedFn()
- refresh stamps cache from completion time
- regression test: slow Coinbase failure + round published mid-request returns 2441.34
@Ayush7614

Ayush7614 commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @Vasanthdev2004 for the review!

Addressed the fallback-clock review: fromChainlink now takes the injected clock and samples it after await feedFn(), and refresh stamps the cache from completion time, so a round published while Coinbase is failing is not rejected as future. Added a regression test (slow Coinbase + round published mid-request returns 2441.34). Verified: typecheck, eslint, and the baseStocks + ethPrice suites (22 tests) pass.

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for fixing the clock handling. The refresh now samples time after the feed request, so a valid round published while the request is in flight is accepted, while genuinely future-dated rounds are still rejected.

Rechecked the current head: all 22 stock/ETH tests passed, along with focused checks for both timing cases. These were isolated tests, not live oracle requests. Current app and contract CI are green.

My earlier requested change is addressed. Approving this version; the price-validation fix makes sense as its own PR.

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