Skip to content

apollo_l1_gas_price_config: Chainlink oracle config - #14983

Merged
asaf-sw merged 1 commit into
main-v0.14.3from
asaf/l1-oracle-a6b-chainlink-config
Aug 20, 2026
Merged

apollo_l1_gas_price_config: Chainlink oracle config#14983
asaf-sw merged 1 commit into
main-v0.14.3from
asaf/l1-oracle-a6b-chainlink-config

Conversation

@asaf-sw

@asaf-sw asaf-sw commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Adds ChainlinkOracleConfig, the mechanics half of the Chainlink feed source: the two mainnet proxy feed_addresses, the FreshnessWindow a round's updated_at must fall in, a 900 second sampling_interval_seconds and a 60 second failure_retry_interval_seconds, with a validator keeping the forward freshness bound strictly below the backward one.

A6b of the split of #14942. The rate bounds that sit alongside these fields on the source branch were extracted into RateBoundsConfig in the base PR, so this carries only Chainlink-specific fields.

  • sampling_interval_seconds is deliberately a new field: the source branch read the HTTP source's lag_interval_seconds instead, so the Chainlink path no longer reaches into the HTTP config and deleting that source later stays a pure deletion.
  • Deferred: nothing reads this config. The client that reads the feeds arrives in A9 and the source is wired in after that. The config is still un-nested, so config_schema.json and the app configs are untouched until B2 (apollo_l1_gas_price_config,apollo_deployments,apollo_node: nest the Chainlink config #14994).

Detailed Summary for AI Bots

Adds ChainlinkOracleConfig, the mechanics half of the Chainlink price-feed source: the two mainnet proxy feed_addresses, the FreshnessWindow a round's updated_at must fall in, and the two intervals the source is read on.

The rate bounds that live alongside these fields in the original branch were already extracted into RateBoundsConfig in the base PR, so this one carries only the Chainlink-specific fields.

Fields

  • eth_usd_feed_address / strk_usd_feed_address: Chainlink proxy addresses on Starknet mainnet. The proxies rather than the aggregators behind them, because aggregators are rotated without notice.
  • freshness: max_staleness_seconds (the 24h feed heartbeat plus an hour of margin) and max_future_updated_at_seconds (300s of clock skew). A schema-level validator keeps the forward bound strictly below the backward one, since both are plain second counts and exchanging them would otherwise pass every per-field check.
  • sampling_interval_seconds: 900 by default, range(min = 1).
  • failure_retry_interval_seconds: 60 by default, range(min = 1).

sampling_interval_seconds is deliberately new

In the source branch, the Chainlink factory read the sampling interval off the HTTP source's ExchangeRateOracleConfig::lag_interval_seconds. This PR replaces that read with a field of its own, for two reasons:

  • The Chainlink path never has to reach into the HTTP source's config to learn how often to sample.
  • Deleting the HTTP source later stays a pure deletion, with nothing to rehome first.

It is one field on ChainlinkOracleConfig, not one per feed, since both feeds are sampled on the same interval.

Scope

Nothing reads this config yet; the client that reads the feeds arrives in a later PR, and the source is wired in after that. config_schema.json and the apollo_deployments app configs are untouched here as well: the config is still un-nested, and it reaches the schema and the app configs when it is nested under L1GasPriceProviderConfig.

Tests cover the default validating, the two zero-interval rejections, the sampling_interval_seconds range at 0 and 1, the default freshness window pinned by direction, and an inverted or equal freshness window.

@reviewable-StarkWare

Copy link
Copy Markdown

This change is Reviewable

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown

@matanl-starkware matanl-starkware 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.

@matanl-starkware made 2 comments.
Reviewable status: 0 of 4 files reviewed, 2 unresolved discussions (waiting on asaf-sw).


crates/apollo_l1_gas_price_config/src/config.rs line 297 at r2 (raw file):

/// Catches exchanged bounds: the forward bound covers only clock skew, so it must sit strictly
/// below the backward one, which covers a full heartbeat.

Please explain with respect to the member names in plain English

Code quote:

/// Catches exchanged bounds: the forward bound covers only clock skew, so it must sit strictly
/// below the backward one, which covers a full heartbeat.

crates/apollo_l1_gas_price_config/src/config.rs line 342 at r2 (raw file):

pub struct ChainlinkOracleConfig {
    /// Quotes USD per ETH.
    pub eth_usd_feed_address: ContractAddress,

I don't understand why we don't have this type of struct:
pub struct PairConfig (choose appropriate name):
{
pub address: ContractAddress,
pub bounds: RateBounds,
}

Code quote:

pub eth_usd_feed_address: ContractAddress,

@asaf-sw

asaf-sw commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

Migrated review comment from #14944, closed in favor of this split.

@matanl-starkware wrote on crates/apollo_deployments/resources/app_configs/l1_gas_price_provider_config.json line 3, on .lag_interval_seconds": 60, (original thread):

"lag interval" vs. "lag margin". I lost it.

This PR owns the Chainlink cadence param the comment landed on. It is ChainlinkOracleConfig::sampling_interval_seconds here, alongside failure_retry_interval_seconds, while lag_interval_seconds stays on the HTTP ExchangeRateOracleConfig and lag_margin_seconds stays on L1GasPriceProviderConfig.

@asaf-sw

asaf-sw commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

Migrated review comment from #14944, closed in favor of this split.

@matanl-starkware wrote on crates/apollo_deployments/resources/app_configs/l1_gas_price_provider_config.json line 13 (original thread), on the flat block of chainlink_oracle_config.* keys (lag_interval_seconds, max_cache_size, max_eth_to_fri_rate_micro_strk, max_eth_usd_price_micro_usd, max_future_updated_at_seconds, max_staleness_seconds, max_strk_usd_price_micro_usd, min_eth_to_fri_rate_micro_strk, min_eth_usd_price_micro_usd, min_strk_usd_price_micro_usd, strk_usd_feed_address):

This section is scrambled.
What about defining struct per rate: (address, min, max) + common (staleness, future update, etc)?

This PR owns the addresses and the common params: ChainlinkOracleConfig holds eth_usd_feed_address and strk_usd_feed_address, with the shared freshness params under a nested freshness (max_staleness_seconds, max_future_updated_at_seconds). The per-pair (min, max) half is RateBoundsConfig in #14982, and the nesting under chainlink_oracle_config is #14994. The same comment is copied on both.

@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-a6a-rate-bounds branch from f474fcb to 32735ed Compare August 19, 2026 12:22
@asaf-sw
asaf-sw changed the base branch from asaf/l1-oracle-a6a-rate-bounds to graphite-base/14983 August 19, 2026 13:19
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-a6b-chainlink-config branch from dc4d488 to 2a1b5b4 Compare August 19, 2026 18:32
@asaf-sw
asaf-sw changed the base branch from graphite-base/14983 to asaf/l1-oracle-a6a-rate-bounds August 19, 2026 18:38
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-a6a-rate-bounds branch from 8af1f17 to 1cfb7da Compare August 20, 2026 04:13
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-a6b-chainlink-config branch from 2a1b5b4 to 07b3c1e Compare August 20, 2026 04:13
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-a6a-rate-bounds branch from 1cfb7da to b2a46b1 Compare August 20, 2026 04:43
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-a6b-chainlink-config branch from 07b3c1e to 8c3e021 Compare August 20, 2026 04:43
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-a6a-rate-bounds branch from b2a46b1 to 429f80e Compare August 20, 2026 06:36
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-a6b-chainlink-config branch from 8c3e021 to 28d1bc4 Compare August 20, 2026 06:36

@asaf-sw asaf-sw left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@asaf-sw+AGNT made 2 comments.
Reviewable status: 0 of 4 files reviewed, 2 unresolved discussions (waiting on matanl-starkware).


crates/apollo_l1_gas_price_config/src/config.rs line 297 at r2 (raw file):

Previously, matanl-starkware (Matan Lior) wrote…

Please explain with respect to the member names in plain English

Done, rewritten to name the fields:

/// Rejects a window whose two bounds are swapped: `max_future_updated_at_seconds` covers clock
/// skew, so it must sit strictly below `max_staleness_seconds`, which covers a full feed heartbeat.

crates/apollo_l1_gas_price_config/src/config.rs line 342 at r2 (raw file):

Previously, matanl-starkware (Matan Lior) wrote…

I don't understand why we don't have this type of struct:
pub struct PairConfig (choose appropriate name):
{
pub address: ContractAddress,
pub bounds: RateBounds,
}

Grouping address with bounds does not fit, for one concrete reason: there are three bounded pairs and only two feeds. AllRateBoundsConfig bounds eth_usd, strk_usd and the derived eth_strk, which has no feed address and never will, since it is computed from the two USD legs. A PairConfig { address, bounds } has no value to put in address for that pair.

The two also have different lifetimes and owners, which is why #14982 split them out in the first place: the bounds describe the rate ("ETH is plausibly $20 to $50,000" is true whoever reports it) and survive the HTTP oracle removal untouched, while the feed addresses are Chainlink-specific and die with Chainlink. Folding them back means moving live config keys the day either one goes.

The grouping you are describing does exist, one layer up, at the point of use. ChainlinkFeeds::eth_usd_feed(&bounds_config) / strk_usd_feed(&bounds_config) assemble exactly that struct per pair, in #14990:

pub(super) struct PairFeed {
    feed_address: ContractAddress,
    bounds: RateBounds,
    freshness: FreshnessWindow,
}

So read_feed takes one value carrying the address, the bounds and the pair, and there is one method per pair Chainlink actually quotes, which makes a read for the derived pair unrepresentable. I added a line to ChainlinkOracleConfig's doc pointing at this, since the split is not obvious from the struct alone:

/// Holds only what is Chainlink-specific: the bounds a feed's answer is judged against describe
/// the rate rather than the source, so they live in `AllRateBoundsConfig`, which also bounds the
/// derived ETH/STRK pair that has no feed of its own.

Happy to group the per-feed Chainlink settings if more of them ever appear, but today the address is the only one, so the struct would be a one-field wrapper.

@matanl-starkware matanl-starkware 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.

@matanl-starkware reviewed 4 files and all commit messages, and resolved 2 discussions.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on asaf-sw).

@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-a6a-rate-bounds branch from 429f80e to d24c00c Compare August 20, 2026 07:35
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-a6b-chainlink-config branch from 28d1bc4 to b55e49f Compare August 20, 2026 07:35

@asaf-sw asaf-sw left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@asaf-sw reviewed 4 files and all commit messages.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on asaf-sw).

@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-a6a-rate-bounds branch from d24c00c to cc9f8cf Compare August 20, 2026 09:20
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-a6b-chainlink-config branch from b55e49f to 194eef0 Compare August 20, 2026 09:20
@asaf-sw
asaf-sw changed the base branch from asaf/l1-oracle-a6a-rate-bounds to main-v0.14.3 August 20, 2026 10:05
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-a6b-chainlink-config branch from 194eef0 to 7bf91db Compare August 20, 2026 10:06
@graphite-app

graphite-app Bot commented Aug 20, 2026

Copy link
Copy Markdown

Merge activity

  • Aug 20, 10:06 AM UTC: Graphite rebased this pull request, because this pull request is set to merge when ready.

@asaf-sw
asaf-sw added this pull request to the merge queue Aug 20, 2026
Merged via the queue into main-v0.14.3 with commit 357ff44 Aug 20, 2026
41 checks passed
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.

3 participants