Skip to content

Serve each feed at any decimal scale up to its stored precision - #174

Closed
Fletch153 wants to merge 11 commits into
mainfrom
feature/DF-25429/decimal-views
Closed

Serve each feed at any decimal scale up to its stored precision#174
Fletch153 wants to merge 11 commits into
mainfrom
feature/DF-25429/decimal-views

Conversation

@Fletch153

Copy link
Copy Markdown
Collaborator

Summary

  • One feed is stored once, keyed by a canonical ID (data ID with the decimals byte zeroed); callers address it at any scale ≤ the stored decimals.
  • The cache stores and returns raw answers only; the proxy downscales to the requested decimals on read.

Changes

  • Add data-feeds cache, proxy, and common contracts.
  • data-feeds-common: data ID layout — byte 7 encodes requested decimals as 0x20 + decimals.
  • Cache: all storage (config, permissions, state, rounds) keyed by the canonical ID; canonical() / is_same_feed() hidden inside feed.rs; feed configs are permanent once created.
  • Proxy: reads stored decimals from the cache, downscales answers, rejects requested > stored with UnsupportedDecimals, errors on truncation to zero.
  • contracts/data-feeds/CONTEXT.md glossary; fixture regen README (pin stellar-cli v25.x).

Testing

  • cargo test in contracts/data-feeds — 230/230 pass (183 cache, 35 proxy, 12 common), incl. upgrade tests against a self-upgrade wasm fixture rebuilt from current source.

Notes

  • Building the cache wasm requires stellar-cli v25.x (soroban-sdk 26.1.0 spec-shaking gate); v23 and v27 both fail — see test_fixtures/README.md.

Self-contained nested cargo workspace at contracts/data-feeds:
cache (rounds, permissions, per-feed freeze), proxy, and shared
ownable/upgradeable/token-recovery crates. Literal BytesN types
at ABI positions; Bound carries explicit discriminants.
A DataId carries the decimals in byte 7, so one feed was reachable at
one scale only and a second scale meant writing the feed twice. Key
feed storage by the DataId with that byte cleared, and derive any other
scale from the stored answer at read time. Issuing an 8 decimal view of
an 18 decimal feed becomes a docs change, not a deployment.

- Add domain/data_id.rs owning the layout: CanonicalId and decimals_of
- Key config, permission, state and round stores by CanonicalId
- Record the canonical decimals in StoredConfig at configure time
- Downscale answers in latest, get_round, round_range and find_round
- Reject upscaling, an out of range decimals byte, and a downscale that
  would truncate a non-zero answer to zero
- Drop reports addressed at a non-canonical scale, with an event
The cache masked the decimals byte to key storage, then scaled answers
itself while decimals() reported the scale the caller asked for. Reading
those two together was inconsistent for anyone holding the raw cache
client. The cache now serves what it stores and reports the scale it
stores at; the proxy derives lower scales for the id it was given.

- Move decimals_of to the cache's public interface so the proxy can read
  the scale an id addresses
- decimals() returns the scale recorded at registration, not the request
- Cache reads serve stored answers unscaled and cannot fail on scale
- Proxy downscales in latest_round and get_round, and reports the scale
  it scaled to
- Proxy rejects a scale above the stored one, a decimals byte out of
  range, and a downscale that would truncate a non-zero answer to zero
- Re-registering a canonical id at another scale fails with
  DecimalsMismatch, so a feed's scale is immutable once set
A config could be removed, which left rounds and feed state behind and
reopened the one hole in the immutable-scale guard: remove then re-add
at another decimals byte would give a feed whose history is half at one
scale and half at another. Configs are now create-or-update only. A feed
is taken out of service by freezing it, and its entries expire on their
own once the workflow stops writing.

- Drop remove_feed_configs from the admin interface and the contract
- Drop the FeedConfigRemoved event; FeedConfigSet carries the new
  description and permissions on both create and update
- configure returns unit, since a config can no longer be absent
Only remove_feed_configs raised it, so nothing produces it now that a
config cannot be removed. Reads report an absent feed as None or an
empty Vec, and a report against an unconfigured feed publishes
InvalidUpdatePermission rather than failing. Wire numbering now skips
102.
record read it twice: once to check the report addresses the feed at the
scale it is stored at, and again to extend the config and permission
TTLs. One read serves both, so the decimals check costs nothing over the
TTL extension it already needed.
It is a storage key type and storage.rs was its only real consumer, so a
separate domain module bought nothing. Kept in storage rather than feed
so the dependency stays one way: feed uses storage, not both ways.
decimals_of sat in the cache's interface module, which is the ABI surface,
and the contract had to read the decimals byte itself before configuring a
feed. The layout now lives in data-feeds-common, where both contracts can
reach one definition, and deriving the scale is the feed module's job.

- Add data_feeds_common::data_id with DECIMALS_BYTE and decimals_of
- feed::configure derives and validates the scale, returning it for the
  FeedConfigSet event, so the contract never touches the byte
- Drop the cache's interface::data_id module and its re-export
- Replace the CanonicalId newtype in storage with canonical() and
  is_same_feed() in feed.rs; store keys stay the zeroed-decimals ID
- Re-home the canonical-ID unit tests under feed::canonical and cover
  is_same_feed
- Rebuild cache_self_upgrade.wasm from current source; the stale
  fixture predated StoredConfig and broke both upgrade tests
- Document fixture regeneration (pin stellar-cli v25.x) and add the
  data-feeds CONTEXT.md glossary
@Fletch153

Copy link
Copy Markdown
Collaborator Author

Superseded by #173, which stacks this branch on the audit branch feature/DF-25429/data-feeds-contracts (#161) instead of main.

@Fletch153 Fletch153 closed this Aug 6, 2026
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.

1 participant