diff --git a/lazer/stellar/README.md b/lazer/stellar/README.md index a8f2c4b1..498e2eb8 100644 --- a/lazer/stellar/README.md +++ b/lazer/stellar/README.md @@ -10,7 +10,8 @@ integration on Stellar: 1. **Verify** a signed Lazer update via the deployed `pyth-lazer-stellar` verifier contract. 2. **Parse** the verified payload with the published [`pyth-lazer-stellar-sdk`](https://crates.io/crates/pyth-lazer-stellar-sdk). -3. **Freshness-check** the feed's update timestamp against a deployment-configured threshold. +3. **Freshness-check** the feed's update timestamp against a deployment-configured threshold, and + reject any update whose timestamp is not strictly newer than the stored price (monotonic updates). 4. **Store / retrieve** the latest price for a single configured feed. This is an example, not a production library — it tracks exactly one feed and keeps only the most diff --git a/lazer/stellar/src/error.rs b/lazer/stellar/src/error.rs index 0c6b94f2..ec936b02 100644 --- a/lazer/stellar/src/error.rs +++ b/lazer/stellar/src/error.rs @@ -14,6 +14,7 @@ pub enum Error { PriceNotInitialized = 6, Overflow = 7, ParseError = 8, + PriceOutdated = 9, } impl From for Error { diff --git a/lazer/stellar/src/lib.rs b/lazer/stellar/src/lib.rs index 16a6adb4..0542e852 100644 --- a/lazer/stellar/src/lib.rs +++ b/lazer/stellar/src/lib.rs @@ -34,7 +34,8 @@ impl PythLazerExample { } /// Verify a signed Pyth Lazer update, check it is fresh, and store the - /// latest price for the configured feed. + /// latest price for the configured feed. Rejects the update if its feed + /// timestamp is not strictly newer than the currently stored price. pub fn update_price(env: Env, payload: Bytes) -> Result<(), Error> { let lazer = state::get_lazer(&env); let feed_id = state::get_feed_id(&env); @@ -62,6 +63,15 @@ impl PythLazerExample { return Err(Error::PriceStale); } + // Never overwrite a newer price with an older one: the update's feed + // timestamp must strictly exceed the stored one. This keeps the stored + // price monotonic even if updates arrive out of order. + if let Some(stored) = state::get_latest_price(&env) { + if feed_ts_us <= stored.timestamp_us { + return Err(Error::PriceOutdated); + } + } + let price = feed.price.ok_or(Error::PriceMissing)?; let exponent = i32::from(feed.exponent.ok_or(Error::ExponentMissing)?);