170 lines
5.6 KiB
Rust
170 lines
5.6 KiB
Rust
// file: crates/ksp-offchain-transport-lib/src/market_price_observation.rs
|
|
// version: 3
|
|
|
|
/// Maximum UTF-8 byte length accepted for safe provider provenance.
|
|
pub const MARKET_PRICE_PROVENANCE_MAX_BYTES: usize = 256;
|
|
|
|
/// Millisecond UTC timestamp used by provider-neutral public projections.
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize)]
|
|
pub struct MarketPriceTimestamp {
|
|
unix_millis: u64,
|
|
}
|
|
|
|
impl MarketPriceTimestamp {
|
|
/// Creates a UTC timestamp from whole milliseconds since Unix epoch.
|
|
#[must_use]
|
|
pub const fn from_unix_millis(unix_millis: u64) -> Self {
|
|
return Self { unix_millis };
|
|
}
|
|
|
|
/// Returns whole milliseconds since Unix epoch.
|
|
#[must_use]
|
|
pub const fn unix_millis(&self) -> u64 {
|
|
return self.unix_millis;
|
|
}
|
|
}
|
|
|
|
/// Safe bounded provenance supplied by one provider adapter.
|
|
#[derive(Clone, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
#[serde(try_from = "std::string::String", into = "std::string::String")]
|
|
pub struct MarketPriceProvenance(std::string::String);
|
|
|
|
impl MarketPriceProvenance {
|
|
/// Creates bounded non-empty provenance without accepting control characters.
|
|
pub fn new(value: impl std::convert::Into<std::string::String>) -> ksp_core_lib::Result<Self> {
|
|
let value = value.into();
|
|
if !valid_provenance(value.as_str()) {
|
|
return std::result::Result::Err(observation_error());
|
|
}
|
|
return std::result::Result::Ok(Self(value));
|
|
}
|
|
|
|
/// Returns the safe provider provenance.
|
|
#[must_use]
|
|
pub fn as_str(&self) -> &str {
|
|
return self.0.as_str();
|
|
}
|
|
}
|
|
|
|
impl std::convert::TryFrom<std::string::String> for MarketPriceProvenance {
|
|
type Error = ksp_core_lib::Error;
|
|
|
|
fn try_from(value: std::string::String) -> std::result::Result<Self, Self::Error> {
|
|
return crate::MarketPriceProvenance::new(value);
|
|
}
|
|
}
|
|
|
|
impl std::convert::From<MarketPriceProvenance> for std::string::String {
|
|
fn from(value: MarketPriceProvenance) -> Self {
|
|
return value.0;
|
|
}
|
|
}
|
|
|
|
/// Public V1 SOL/USD observation normalized by Off-chain Transport.
|
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
|
|
pub struct MarketPriceObservation {
|
|
pair: crate::MarketPricePair,
|
|
price: crate::MarketPriceDecimal,
|
|
provider_id: crate::MarketPriceProviderId,
|
|
provider_timestamp: std::option::Option<crate::MarketPriceTimestamp>,
|
|
provenance: crate::MarketPriceProvenance,
|
|
received_at: crate::MarketPriceTimestamp,
|
|
request_started_at: crate::MarketPriceTimestamp,
|
|
semantics: crate::MarketPriceSemantics,
|
|
}
|
|
|
|
impl MarketPriceObservation {
|
|
/// Creates one successful normalized SOL/USD observation.
|
|
pub fn new(
|
|
provider_id: crate::MarketPriceProviderId,
|
|
price: crate::MarketPriceDecimal,
|
|
semantics: crate::MarketPriceSemantics,
|
|
request_started_at: crate::MarketPriceTimestamp,
|
|
received_at: crate::MarketPriceTimestamp,
|
|
provider_timestamp: std::option::Option<crate::MarketPriceTimestamp>,
|
|
provenance: crate::MarketPriceProvenance,
|
|
) -> ksp_core_lib::Result<Self> {
|
|
if received_at < request_started_at {
|
|
return std::result::Result::Err(observation_error());
|
|
}
|
|
return std::result::Result::Ok(Self {
|
|
pair: crate::MarketPricePair::SolUsd,
|
|
price,
|
|
provider_id,
|
|
provider_timestamp,
|
|
provenance,
|
|
received_at,
|
|
request_started_at,
|
|
semantics,
|
|
});
|
|
}
|
|
|
|
/// Returns the only V1 pair represented by this observation.
|
|
#[must_use]
|
|
pub const fn pair(&self) -> crate::MarketPricePair {
|
|
return self.pair;
|
|
}
|
|
|
|
/// Returns the exact positive SOL/USD price.
|
|
#[must_use]
|
|
pub const fn price(&self) -> crate::MarketPriceDecimal {
|
|
return self.price;
|
|
}
|
|
|
|
/// Returns the opaque provider identifier.
|
|
#[must_use]
|
|
pub const fn provider_id(&self) -> &crate::MarketPriceProviderId {
|
|
return &self.provider_id;
|
|
}
|
|
|
|
/// Returns a provider timestamp only when the provider adapter has a real price-time field.
|
|
#[must_use]
|
|
pub const fn provider_timestamp(&self) -> std::option::Option<crate::MarketPriceTimestamp> {
|
|
return self.provider_timestamp;
|
|
}
|
|
|
|
/// Returns safe provider provenance.
|
|
#[must_use]
|
|
pub const fn provenance(&self) -> &crate::MarketPriceProvenance {
|
|
return &self.provenance;
|
|
}
|
|
|
|
/// Returns the KSP wall-clock receipt timestamp.
|
|
#[must_use]
|
|
pub const fn received_at(&self) -> crate::MarketPriceTimestamp {
|
|
return self.received_at;
|
|
}
|
|
|
|
/// Returns the KSP wall-clock request-start timestamp.
|
|
#[must_use]
|
|
pub const fn request_started_at(&self) -> crate::MarketPriceTimestamp {
|
|
return self.request_started_at;
|
|
}
|
|
|
|
/// Returns the provider-specific semantic class retained by the normalized observation.
|
|
#[must_use]
|
|
pub const fn semantics(&self) -> crate::MarketPriceSemantics {
|
|
return self.semantics;
|
|
}
|
|
}
|
|
|
|
fn observation_error() -> ksp_core_lib::Error {
|
|
return ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_OBSERVATION_INVALID, "invalid off-chain market-price observation");
|
|
}
|
|
|
|
fn valid_provenance(value: &str) -> bool {
|
|
if value.is_empty() || value.len() > crate::MARKET_PRICE_PROVENANCE_MAX_BYTES || value.trim() != value {
|
|
return false;
|
|
}
|
|
for character in value.chars() {
|
|
if character.is_control() {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "../unit_tests/market_price_observation.rs"]
|
|
mod tests;
|