v0.2.11-pre.002-fix.003
This commit is contained in:
@@ -6,7 +6,7 @@ resolver = "3"
|
|||||||
members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"]
|
members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.11-pre.2.fix.2"
|
version = "0.2.11-pre.2.fix.3"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
// file: crates/ksp-offchain-transport-lib/src/error.rs
|
// file: crates/ksp-offchain-transport-lib/src/error.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
/// Stable off-chain transport error for an invalid exact decimal price.
|
/// Stable off-chain transport error for an invalid exact market-price decimal.
|
||||||
pub const ERROR_CODE_PRICE_DECIMAL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("offchain_transport", "price_decimal_invalid");
|
pub const ERROR_CODE_MARKET_PRICE_DECIMAL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("offchain_transport", "market_price_decimal_invalid");
|
||||||
/// Stable off-chain transport error for an invalid market-price provider descriptor.
|
|
||||||
pub const ERROR_CODE_PROVIDER_DESCRIPTOR_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("offchain_transport", "provider_descriptor_invalid");
|
|
||||||
/// Stable off-chain transport error for an invalid market-price provider identifier.
|
|
||||||
pub const ERROR_CODE_PROVIDER_ID_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("offchain_transport", "provider_id_invalid");
|
|
||||||
/// Stable off-chain transport error for an invalid normalized market-price observation.
|
/// Stable off-chain transport error for an invalid normalized market-price observation.
|
||||||
pub const ERROR_CODE_PROVIDER_OBSERVATION_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("offchain_transport", "provider_observation_invalid");
|
pub const ERROR_CODE_MARKET_PRICE_OBSERVATION_INVALID: ksp_core_lib::ErrorCode =
|
||||||
|
ksp_core_lib::ErrorCode::new("offchain_transport", "market_price_observation_invalid");
|
||||||
|
/// Stable off-chain transport error for an invalid market-price provider descriptor.
|
||||||
|
pub const ERROR_CODE_MARKET_PRICE_PROVIDER_DESCRIPTOR_INVALID: ksp_core_lib::ErrorCode =
|
||||||
|
ksp_core_lib::ErrorCode::new("offchain_transport", "market_price_provider_descriptor_invalid");
|
||||||
|
/// Stable off-chain transport error for an invalid market-price provider identifier.
|
||||||
|
pub const ERROR_CODE_MARKET_PRICE_PROVIDER_ID_INVALID: ksp_core_lib::ErrorCode =
|
||||||
|
ksp_core_lib::ErrorCode::new("offchain_transport", "market_price_provider_id_invalid");
|
||||||
/// Stable off-chain transport error for invalid common market-price provider settings.
|
/// Stable off-chain transport error for invalid common market-price provider settings.
|
||||||
pub const ERROR_CODE_PROVIDER_SETTINGS_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("offchain_transport", "provider_settings_invalid");
|
pub const ERROR_CODE_MARKET_PRICE_PROVIDER_SETTINGS_INVALID: ksp_core_lib::ErrorCode =
|
||||||
|
ksp_core_lib::ErrorCode::new("offchain_transport", "market_price_provider_settings_invalid");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-offchain-transport-lib/src/lib.rs
|
// file: crates/ksp-offchain-transport-lib/src/lib.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -21,61 +21,61 @@ mod market_price_provider;
|
|||||||
mod market_price_settings;
|
mod market_price_settings;
|
||||||
|
|
||||||
/// Stable error code for an invalid exact decimal price.
|
/// Stable error code for an invalid exact decimal price.
|
||||||
pub use self::error::ERROR_CODE_PRICE_DECIMAL_INVALID;
|
pub use self::error::ERROR_CODE_MARKET_PRICE_DECIMAL_INVALID;
|
||||||
/// Stable error code for an invalid provider descriptor.
|
|
||||||
pub use self::error::ERROR_CODE_PROVIDER_DESCRIPTOR_INVALID;
|
|
||||||
/// Stable error code for an invalid provider identifier.
|
|
||||||
pub use self::error::ERROR_CODE_PROVIDER_ID_INVALID;
|
|
||||||
/// Stable error code for an invalid provider-neutral observation.
|
/// Stable error code for an invalid provider-neutral observation.
|
||||||
pub use self::error::ERROR_CODE_PROVIDER_OBSERVATION_INVALID;
|
pub use self::error::ERROR_CODE_MARKET_PRICE_OBSERVATION_INVALID;
|
||||||
|
/// Stable error code for an invalid provider descriptor.
|
||||||
|
pub use self::error::ERROR_CODE_MARKET_PRICE_PROVIDER_DESCRIPTOR_INVALID;
|
||||||
|
/// Stable error code for an invalid provider identifier.
|
||||||
|
pub use self::error::ERROR_CODE_MARKET_PRICE_PROVIDER_ID_INVALID;
|
||||||
/// Stable error code for invalid common provider settings.
|
/// Stable error code for invalid common provider settings.
|
||||||
pub use self::error::ERROR_CODE_PROVIDER_SETTINGS_INVALID;
|
pub use self::error::ERROR_CODE_MARKET_PRICE_PROVIDER_SETTINGS_INVALID;
|
||||||
/// Maximum accepted byte length for one textual decimal input.
|
/// Maximum accepted byte length for one textual decimal input.
|
||||||
pub use self::market_price_decimal::PRICE_DECIMAL_MAX_INPUT_BYTES;
|
pub use self::market_price_decimal::MARKET_PRICE_DECIMAL_MAX_INPUT_BYTES;
|
||||||
/// Maximum decimal scale retained by the canonical SOL/USD price representation.
|
/// Maximum decimal scale retained by the canonical SOL/USD price representation.
|
||||||
pub use self::market_price_decimal::PRICE_DECIMAL_MAX_SCALE;
|
pub use self::market_price_decimal::MARKET_PRICE_DECIMAL_MAX_SCALE;
|
||||||
/// Exact positive decimal value used by the public off-chain price contract.
|
/// Exact positive decimal value used by the public market-price contract.
|
||||||
pub use self::market_price_decimal::PriceDecimal;
|
pub use self::market_price_decimal::MarketPriceDecimal;
|
||||||
/// Maximum safe provenance length attached to one normalized observation.
|
/// Maximum safe provenance length attached to one normalized observation.
|
||||||
pub use self::market_price_observation::PRICE_PROVENANCE_MAX_BYTES;
|
pub use self::market_price_observation::MARKET_PRICE_PROVENANCE_MAX_BYTES;
|
||||||
/// Safe bounded provenance supplied by one provider adapter.
|
|
||||||
pub use self::market_price_observation::PriceProvenance;
|
|
||||||
/// Millisecond UTC timestamp used for request, receipt, provider and cooldown projections.
|
|
||||||
pub use self::market_price_observation::PriceTimestamp;
|
|
||||||
/// Public V1 SOL/USD observation normalized by Off-chain Transport.
|
/// Public V1 SOL/USD observation normalized by Off-chain Transport.
|
||||||
pub use self::market_price_observation::SolUsdPriceObservation;
|
pub use self::market_price_observation::MarketPriceObservation;
|
||||||
|
/// Safe bounded provenance supplied by one provider adapter.
|
||||||
|
pub use self::market_price_observation::MarketPriceProvenance;
|
||||||
|
/// Millisecond UTC timestamp used for request, receipt, provider and cooldown projections.
|
||||||
|
pub use self::market_price_observation::MarketPriceTimestamp;
|
||||||
/// Maximum provider display-name length accepted by descriptors.
|
/// Maximum provider display-name length accepted by descriptors.
|
||||||
pub use self::market_price_provider::PRICE_PROVIDER_DISPLAY_NAME_MAX_BYTES;
|
pub use self::market_price_provider::MARKET_PRICE_PROVIDER_DISPLAY_NAME_MAX_BYTES;
|
||||||
/// Maximum opaque provider identifier length accepted by the public contract.
|
/// Maximum opaque provider identifier length accepted by the public contract.
|
||||||
pub use self::market_price_provider::PRICE_PROVIDER_ID_MAX_BYTES;
|
pub use self::market_price_provider::MARKET_PRICE_PROVIDER_ID_MAX_BYTES;
|
||||||
/// Only price pair exposed by the `0.2.11` V1 public contract.
|
/// Only price pair exposed by the `0.2.11` V1 public contract.
|
||||||
pub use self::market_price_provider::PricePair;
|
pub use self::market_price_provider::MarketPricePair;
|
||||||
/// Generic authentication capability exposed by a configured provider descriptor.
|
/// Generic authentication capability exposed by a configured provider descriptor.
|
||||||
pub use self::market_price_provider::PriceProviderAuthMode;
|
pub use self::market_price_provider::MarketPriceProviderAuthMode;
|
||||||
/// Generic runtime availability state exposed without provider-specific error parsing.
|
/// Generic runtime availability state exposed without provider-specific error parsing.
|
||||||
pub use self::market_price_provider::PriceProviderAvailability;
|
pub use self::market_price_provider::MarketPriceProviderAvailability;
|
||||||
/// Provider capability and presentation descriptor consumed by provider-agnostic callers.
|
/// Provider capability and presentation descriptor consumed by provider-agnostic callers.
|
||||||
pub use self::market_price_provider::PriceProviderDescriptor;
|
pub use self::market_price_provider::MarketPriceProviderDescriptor;
|
||||||
/// Opaque validated provider identifier owned by Off-chain Transport.
|
/// Opaque validated provider identifier owned by Off-chain Transport.
|
||||||
pub use self::market_price_provider::PriceProviderId;
|
pub use self::market_price_provider::MarketPriceProviderId;
|
||||||
/// Long-term provider quota descriptor that is informational rather than an authoritative local counter.
|
/// Long-term provider quota descriptor that is informational rather than an authoritative local counter.
|
||||||
pub use self::market_price_provider::PriceProviderLongTermQuota;
|
pub use self::market_price_provider::MarketPriceProviderLongTermQuota;
|
||||||
/// Period used by a documented long-term provider quota.
|
/// Period used by a documented long-term provider quota.
|
||||||
pub use self::market_price_provider::PriceProviderQuotaPeriod;
|
pub use self::market_price_provider::MarketPriceProviderQuotaPeriod;
|
||||||
/// Unit used by a documented long-term provider quota.
|
/// Unit used by a documented long-term provider quota.
|
||||||
pub use self::market_price_provider::PriceProviderQuotaUnit;
|
pub use self::market_price_provider::MarketPriceProviderQuotaUnit;
|
||||||
/// Generic provider request-limit capability.
|
/// Generic provider request-limit capability.
|
||||||
pub use self::market_price_provider::PriceProviderRateLimit;
|
pub use self::market_price_provider::MarketPriceProviderRateLimit;
|
||||||
/// Shape of one generic provider request-limit capability.
|
/// Shape of one generic provider request-limit capability.
|
||||||
pub use self::market_price_provider::PriceProviderRateLimitKind;
|
pub use self::market_price_provider::MarketPriceProviderRateLimitKind;
|
||||||
/// Scope to which a provider documents one request limit.
|
/// Scope to which a provider documents one request limit.
|
||||||
pub use self::market_price_provider::PriceProviderRateLimitScope;
|
pub use self::market_price_provider::MarketPriceProviderRateLimitScope;
|
||||||
/// Current provider-neutral runtime state projection.
|
/// Current provider-neutral runtime state projection.
|
||||||
pub use self::market_price_provider::PriceProviderState;
|
pub use self::market_price_provider::MarketPriceProviderState;
|
||||||
/// Price semantics retained so consumers never assume all providers report equivalent market values.
|
/// Market-price semantics retained so consumers never assume all providers report equivalent market values.
|
||||||
pub use self::market_price_provider::PriceSemantics;
|
pub use self::market_price_provider::MarketPriceSemantics;
|
||||||
/// Common provider settings shared by provider-specific runtime settings.
|
/// Common provider settings shared by provider-specific runtime settings.
|
||||||
pub use self::market_price_settings::PriceProviderCommonSettings;
|
pub use self::market_price_settings::MarketPriceProviderCommonSettings;
|
||||||
|
|
||||||
/// Owning tracing target for events emitted by Off-chain Transport.
|
/// Owning tracing target for events emitted by Off-chain Transport.
|
||||||
pub(crate) use self::constants::TRACING_TARGET;
|
pub(crate) use self::constants::TRACING_TARGET;
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
// file: crates/ksp-offchain-transport-lib/src/market_price_decimal.rs
|
// file: crates/ksp-offchain-transport-lib/src/market_price_decimal.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
/// Maximum accepted UTF-8 byte length for one textual decimal input.
|
/// Maximum accepted UTF-8 byte length for one textual decimal input.
|
||||||
pub const PRICE_DECIMAL_MAX_INPUT_BYTES: usize = 96;
|
pub const MARKET_PRICE_DECIMAL_MAX_INPUT_BYTES: usize = 96;
|
||||||
/// Maximum scale retained by the canonical exact decimal representation.
|
/// Maximum scale retained by the canonical exact decimal representation.
|
||||||
pub const PRICE_DECIMAL_MAX_SCALE: u8 = 18;
|
pub const MARKET_PRICE_DECIMAL_MAX_SCALE: u8 = 18;
|
||||||
|
|
||||||
/// Exact positive decimal value used for one successful V1 SOL/USD observation.
|
/// Exact positive decimal value used for one successful V1 SOL/USD observation.
|
||||||
///
|
///
|
||||||
/// The value is represented as a positive `u128` coefficient plus a bounded decimal scale. Trailing fractional zeroes are removed during construction, and
|
/// The value is represented as a positive `u128` coefficient plus a bounded decimal scale. Trailing fractional zeroes are removed during construction, and
|
||||||
/// Serde serialization always emits the canonical decimal string instead of an IEEE-754 number.
|
/// Serde serialization always emits the canonical decimal string instead of an IEEE-754 number.
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
pub struct PriceDecimal {
|
pub struct MarketPriceDecimal {
|
||||||
coefficient: u128,
|
coefficient: u128,
|
||||||
scale: u8,
|
scale: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PriceDecimal {
|
impl MarketPriceDecimal {
|
||||||
/// Parses a positive decimal or bounded scientific-notation value without converting through `f64`.
|
/// Parses a positive decimal or bounded scientific-notation value without converting through `f64`.
|
||||||
pub fn parse(source: &str) -> ksp_core_lib::Result<Self> {
|
pub fn parse(source: &str) -> ksp_core_lib::Result<Self> {
|
||||||
if source.is_empty() || source.len() > crate::PRICE_DECIMAL_MAX_INPUT_BYTES || source.trim() != source {
|
if source.is_empty() || source.len() > crate::MARKET_PRICE_DECIMAL_MAX_INPUT_BYTES || source.trim() != source {
|
||||||
return std::result::Result::Err(invalid_decimal_error());
|
return std::result::Result::Err(invalid_decimal_error());
|
||||||
}
|
}
|
||||||
let (significand, exponent) = match split_exponent(source) {
|
let (significand, exponent) = match split_exponent(source) {
|
||||||
@@ -49,7 +49,7 @@ impl PriceDecimal {
|
|||||||
};
|
};
|
||||||
effective_scale = 0;
|
effective_scale = 0;
|
||||||
}
|
}
|
||||||
if effective_scale > i32::from(crate::PRICE_DECIMAL_MAX_SCALE) {
|
if effective_scale > i32::from(crate::MARKET_PRICE_DECIMAL_MAX_SCALE) {
|
||||||
return std::result::Result::Err(invalid_decimal_error());
|
return std::result::Result::Err(invalid_decimal_error());
|
||||||
}
|
}
|
||||||
let mut scale = match u8::try_from(effective_scale) {
|
let mut scale = match u8::try_from(effective_scale) {
|
||||||
@@ -92,21 +92,21 @@ impl PriceDecimal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for PriceDecimal {
|
impl std::fmt::Display for MarketPriceDecimal {
|
||||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
return formatter.write_str(self.to_canonical_string().as_str());
|
return formatter.write_str(self.to_canonical_string().as_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for PriceDecimal {
|
impl std::str::FromStr for MarketPriceDecimal {
|
||||||
type Err = ksp_core_lib::Error;
|
type Err = ksp_core_lib::Error;
|
||||||
|
|
||||||
fn from_str(source: &str) -> std::result::Result<Self, Self::Err> {
|
fn from_str(source: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
return crate::PriceDecimal::parse(source);
|
return crate::MarketPriceDecimal::parse(source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl serde::Serialize for PriceDecimal {
|
impl serde::Serialize for MarketPriceDecimal {
|
||||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
@@ -115,19 +115,19 @@ impl serde::Serialize for PriceDecimal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> serde::Deserialize<'de> for PriceDecimal {
|
impl<'de> serde::Deserialize<'de> for MarketPriceDecimal {
|
||||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||||
where
|
where
|
||||||
D: serde::Deserializer<'de>,
|
D: serde::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
return deserializer.deserialize_str(PriceDecimalVisitor);
|
return deserializer.deserialize_str(MarketPriceDecimalVisitor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PriceDecimalVisitor;
|
struct MarketPriceDecimalVisitor;
|
||||||
|
|
||||||
impl<'de> serde::de::Visitor<'de> for PriceDecimalVisitor {
|
impl<'de> serde::de::Visitor<'de> for MarketPriceDecimalVisitor {
|
||||||
type Value = crate::PriceDecimal;
|
type Value = crate::MarketPriceDecimal;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
return formatter.write_str("a canonicalizable positive decimal string");
|
return formatter.write_str("a canonicalizable positive decimal string");
|
||||||
@@ -137,9 +137,9 @@ impl<'de> serde::de::Visitor<'de> for PriceDecimalVisitor {
|
|||||||
where
|
where
|
||||||
E: serde::de::Error,
|
E: serde::de::Error,
|
||||||
{
|
{
|
||||||
return match crate::PriceDecimal::parse(value) {
|
return match crate::MarketPriceDecimal::parse(value) {
|
||||||
std::result::Result::Ok(decimal) => std::result::Result::Ok(decimal),
|
std::result::Result::Ok(decimal) => std::result::Result::Ok(decimal),
|
||||||
std::result::Result::Err(_) => std::result::Result::Err(E::custom("invalid KSP price decimal")),
|
std::result::Result::Err(_) => std::result::Result::Err(E::custom("invalid KSP market-price decimal")),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ fn checked_multiply_power_of_ten(mut value: u128, exponent: u32) -> std::option:
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn invalid_decimal_error() -> ksp_core_lib::Error {
|
fn invalid_decimal_error() -> ksp_core_lib::Error {
|
||||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_PRICE_DECIMAL_INVALID, "invalid exact price decimal");
|
return ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_DECIMAL_INVALID, "invalid exact market-price decimal");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn significand_digits(significand: &str) -> std::result::Result<(std::string::String, u8), ()> {
|
fn significand_digits(significand: &str) -> std::result::Result<(std::string::String, u8), ()> {
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
// file: crates/ksp-offchain-transport-lib/src/market_price_observation.rs
|
// file: crates/ksp-offchain-transport-lib/src/market_price_observation.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
/// Maximum UTF-8 byte length accepted for safe provider provenance.
|
/// Maximum UTF-8 byte length accepted for safe provider provenance.
|
||||||
pub const PRICE_PROVENANCE_MAX_BYTES: usize = 256;
|
pub const MARKET_PRICE_PROVENANCE_MAX_BYTES: usize = 256;
|
||||||
|
|
||||||
/// Millisecond UTC timestamp used by provider-neutral public projections.
|
/// Millisecond UTC timestamp used by provider-neutral public projections.
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct PriceTimestamp {
|
pub struct MarketPriceTimestamp {
|
||||||
unix_millis: u64,
|
unix_millis: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PriceTimestamp {
|
impl MarketPriceTimestamp {
|
||||||
/// Creates a UTC timestamp from whole milliseconds since Unix epoch.
|
/// Creates a UTC timestamp from whole milliseconds since Unix epoch.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn from_unix_millis(unix_millis: u64) -> Self {
|
pub const fn from_unix_millis(unix_millis: u64) -> Self {
|
||||||
@@ -27,9 +27,9 @@ impl PriceTimestamp {
|
|||||||
/// Safe bounded provenance supplied by one provider adapter.
|
/// Safe bounded provenance supplied by one provider adapter.
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
#[serde(try_from = "std::string::String", into = "std::string::String")]
|
#[serde(try_from = "std::string::String", into = "std::string::String")]
|
||||||
pub struct PriceProvenance(std::string::String);
|
pub struct MarketPriceProvenance(std::string::String);
|
||||||
|
|
||||||
impl PriceProvenance {
|
impl MarketPriceProvenance {
|
||||||
/// Creates bounded non-empty provenance without accepting control characters.
|
/// 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> {
|
pub fn new(value: impl std::convert::Into<std::string::String>) -> ksp_core_lib::Result<Self> {
|
||||||
let value = value.into();
|
let value = value.into();
|
||||||
@@ -46,49 +46,49 @@ impl PriceProvenance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::TryFrom<std::string::String> for PriceProvenance {
|
impl std::convert::TryFrom<std::string::String> for MarketPriceProvenance {
|
||||||
type Error = ksp_core_lib::Error;
|
type Error = ksp_core_lib::Error;
|
||||||
|
|
||||||
fn try_from(value: std::string::String) -> std::result::Result<Self, Self::Error> {
|
fn try_from(value: std::string::String) -> std::result::Result<Self, Self::Error> {
|
||||||
return crate::PriceProvenance::new(value);
|
return crate::MarketPriceProvenance::new(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::From<PriceProvenance> for std::string::String {
|
impl std::convert::From<MarketPriceProvenance> for std::string::String {
|
||||||
fn from(value: PriceProvenance) -> Self {
|
fn from(value: MarketPriceProvenance) -> Self {
|
||||||
return value.0;
|
return value.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Public V1 SOL/USD observation normalized by Off-chain Transport.
|
/// Public V1 SOL/USD observation normalized by Off-chain Transport.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
|
||||||
pub struct SolUsdPriceObservation {
|
pub struct MarketPriceObservation {
|
||||||
pair: crate::PricePair,
|
pair: crate::MarketPricePair,
|
||||||
price: crate::PriceDecimal,
|
price: crate::MarketPriceDecimal,
|
||||||
provider_id: crate::PriceProviderId,
|
provider_id: crate::MarketPriceProviderId,
|
||||||
provider_timestamp: std::option::Option<crate::PriceTimestamp>,
|
provider_timestamp: std::option::Option<crate::MarketPriceTimestamp>,
|
||||||
provenance: crate::PriceProvenance,
|
provenance: crate::MarketPriceProvenance,
|
||||||
received_at: crate::PriceTimestamp,
|
received_at: crate::MarketPriceTimestamp,
|
||||||
request_started_at: crate::PriceTimestamp,
|
request_started_at: crate::MarketPriceTimestamp,
|
||||||
semantics: crate::PriceSemantics,
|
semantics: crate::MarketPriceSemantics,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SolUsdPriceObservation {
|
impl MarketPriceObservation {
|
||||||
/// Creates one successful normalized SOL/USD observation.
|
/// Creates one successful normalized SOL/USD observation.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
provider_id: crate::PriceProviderId,
|
provider_id: crate::MarketPriceProviderId,
|
||||||
price: crate::PriceDecimal,
|
price: crate::MarketPriceDecimal,
|
||||||
semantics: crate::PriceSemantics,
|
semantics: crate::MarketPriceSemantics,
|
||||||
request_started_at: crate::PriceTimestamp,
|
request_started_at: crate::MarketPriceTimestamp,
|
||||||
received_at: crate::PriceTimestamp,
|
received_at: crate::MarketPriceTimestamp,
|
||||||
provider_timestamp: std::option::Option<crate::PriceTimestamp>,
|
provider_timestamp: std::option::Option<crate::MarketPriceTimestamp>,
|
||||||
provenance: crate::PriceProvenance,
|
provenance: crate::MarketPriceProvenance,
|
||||||
) -> ksp_core_lib::Result<Self> {
|
) -> ksp_core_lib::Result<Self> {
|
||||||
if received_at < request_started_at {
|
if received_at < request_started_at {
|
||||||
return std::result::Result::Err(observation_error());
|
return std::result::Result::Err(observation_error());
|
||||||
}
|
}
|
||||||
return std::result::Result::Ok(Self {
|
return std::result::Result::Ok(Self {
|
||||||
pair: crate::PricePair::SolUsd,
|
pair: crate::MarketPricePair::SolUsd,
|
||||||
price,
|
price,
|
||||||
provider_id,
|
provider_id,
|
||||||
provider_timestamp,
|
provider_timestamp,
|
||||||
@@ -101,59 +101,59 @@ impl SolUsdPriceObservation {
|
|||||||
|
|
||||||
/// Returns the only V1 pair represented by this observation.
|
/// Returns the only V1 pair represented by this observation.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn pair(&self) -> crate::PricePair {
|
pub const fn pair(&self) -> crate::MarketPricePair {
|
||||||
return self.pair;
|
return self.pair;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the exact positive SOL/USD price.
|
/// Returns the exact positive SOL/USD price.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn price(&self) -> crate::PriceDecimal {
|
pub const fn price(&self) -> crate::MarketPriceDecimal {
|
||||||
return self.price;
|
return self.price;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the opaque provider identifier.
|
/// Returns the opaque provider identifier.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn provider_id(&self) -> &crate::PriceProviderId {
|
pub const fn provider_id(&self) -> &crate::MarketPriceProviderId {
|
||||||
return &self.provider_id;
|
return &self.provider_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a provider timestamp only when the provider adapter has a real price-time field.
|
/// Returns a provider timestamp only when the provider adapter has a real price-time field.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn provider_timestamp(&self) -> std::option::Option<crate::PriceTimestamp> {
|
pub const fn provider_timestamp(&self) -> std::option::Option<crate::MarketPriceTimestamp> {
|
||||||
return self.provider_timestamp;
|
return self.provider_timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns safe provider provenance.
|
/// Returns safe provider provenance.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn provenance(&self) -> &crate::PriceProvenance {
|
pub const fn provenance(&self) -> &crate::MarketPriceProvenance {
|
||||||
return &self.provenance;
|
return &self.provenance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the KSP wall-clock receipt timestamp.
|
/// Returns the KSP wall-clock receipt timestamp.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn received_at(&self) -> crate::PriceTimestamp {
|
pub const fn received_at(&self) -> crate::MarketPriceTimestamp {
|
||||||
return self.received_at;
|
return self.received_at;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the KSP wall-clock request-start timestamp.
|
/// Returns the KSP wall-clock request-start timestamp.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn request_started_at(&self) -> crate::PriceTimestamp {
|
pub const fn request_started_at(&self) -> crate::MarketPriceTimestamp {
|
||||||
return self.request_started_at;
|
return self.request_started_at;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the provider-specific semantic class retained by the normalized observation.
|
/// Returns the provider-specific semantic class retained by the normalized observation.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn semantics(&self) -> crate::PriceSemantics {
|
pub const fn semantics(&self) -> crate::MarketPriceSemantics {
|
||||||
return self.semantics;
|
return self.semantics;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn observation_error() -> ksp_core_lib::Error {
|
fn observation_error() -> ksp_core_lib::Error {
|
||||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_PROVIDER_OBSERVATION_INVALID, "invalid off-chain price observation");
|
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 {
|
fn valid_provenance(value: &str) -> bool {
|
||||||
if value.is_empty() || value.len() > crate::PRICE_PROVENANCE_MAX_BYTES || value.trim() != value {
|
if value.is_empty() || value.len() > crate::MARKET_PRICE_PROVENANCE_MAX_BYTES || value.trim() != value {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for character in value.chars() {
|
for character in value.chars() {
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
// file: crates/ksp-offchain-transport-lib/src/market_price_provider.rs
|
// file: crates/ksp-offchain-transport-lib/src/market_price_provider.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
/// Maximum UTF-8 byte length of one provider display name.
|
/// Maximum UTF-8 byte length of one provider display name.
|
||||||
pub const PRICE_PROVIDER_DISPLAY_NAME_MAX_BYTES: usize = 96;
|
pub const MARKET_PRICE_PROVIDER_DISPLAY_NAME_MAX_BYTES: usize = 96;
|
||||||
/// Maximum byte length of one opaque provider identifier.
|
/// Maximum byte length of one opaque provider identifier.
|
||||||
pub const PRICE_PROVIDER_ID_MAX_BYTES: usize = 64;
|
pub const MARKET_PRICE_PROVIDER_ID_MAX_BYTES: usize = 64;
|
||||||
|
|
||||||
/// Only price pair exposed by the `0.2.11` V1 public contract.
|
/// Only price pair exposed by the `0.2.11` V1 public contract.
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum PricePair {
|
pub enum MarketPricePair {
|
||||||
/// Native SOL quoted directly in US dollars according to one provider's documented semantics.
|
/// Native SOL quoted directly in US dollars according to one provider's documented semantics.
|
||||||
SolUsd,
|
SolUsd,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PricePair {
|
impl MarketPricePair {
|
||||||
/// Returns the stable human-readable pair code.
|
/// Returns the stable human-readable pair code.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn code(&self) -> &'static str {
|
pub const fn code(&self) -> &'static str {
|
||||||
@@ -24,10 +24,10 @@ impl PricePair {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Price semantics retained so normalized observations do not imply cross-provider equivalence.
|
/// Market-price semantics retained so normalized observations do not imply cross-provider equivalence.
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum PriceSemantics {
|
pub enum MarketPriceSemantics {
|
||||||
/// Aggregated market price produced by a multi-market data provider.
|
/// Aggregated market price produced by a multi-market data provider.
|
||||||
AggregatedMarket,
|
AggregatedMarket,
|
||||||
/// USD price associated with one explicitly configured DEX pair.
|
/// USD price associated with one explicitly configured DEX pair.
|
||||||
@@ -43,7 +43,7 @@ pub enum PriceSemantics {
|
|||||||
/// Generic authentication capability exposed by one configured provider.
|
/// Generic authentication capability exposed by one configured provider.
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum PriceProviderAuthMode {
|
pub enum MarketPriceProviderAuthMode {
|
||||||
/// No credential is required for the configured access mode.
|
/// No credential is required for the configured access mode.
|
||||||
None,
|
None,
|
||||||
/// Provider accepts an API key but also supports an unauthenticated mode selected by configuration.
|
/// Provider accepts an API key but also supports an unauthenticated mode selected by configuration.
|
||||||
@@ -55,7 +55,7 @@ pub enum PriceProviderAuthMode {
|
|||||||
/// Scope to which a provider documents a request limit.
|
/// Scope to which a provider documents a request limit.
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum PriceProviderRateLimitScope {
|
pub enum MarketPriceProviderRateLimitScope {
|
||||||
/// Limit is associated with the configured account or API key.
|
/// Limit is associated with the configured account or API key.
|
||||||
Account,
|
Account,
|
||||||
/// Limit is associated with the source IP address.
|
/// Limit is associated with the source IP address.
|
||||||
@@ -69,7 +69,7 @@ pub enum PriceProviderRateLimitScope {
|
|||||||
/// Shape of one generic provider request-limit capability.
|
/// Shape of one generic provider request-limit capability.
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum PriceProviderRateLimitKind {
|
pub enum MarketPriceProviderRateLimitKind {
|
||||||
/// Dynamic or server-driven limit that cannot be represented as one safe fixed local cadence.
|
/// Dynamic or server-driven limit that cannot be represented as one safe fixed local cadence.
|
||||||
Dynamic,
|
Dynamic,
|
||||||
/// Locally enforceable fixed request budget over a documented window.
|
/// Locally enforceable fixed request budget over a documented window.
|
||||||
@@ -78,23 +78,28 @@ pub enum PriceProviderRateLimitKind {
|
|||||||
|
|
||||||
/// Generic provider request-limit capability with validated fixed-limit values.
|
/// Generic provider request-limit capability with validated fixed-limit values.
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize)]
|
||||||
pub struct PriceProviderRateLimit {
|
pub struct MarketPriceProviderRateLimit {
|
||||||
burst: std::option::Option<u32>,
|
burst: std::option::Option<u32>,
|
||||||
kind: crate::PriceProviderRateLimitKind,
|
kind: crate::MarketPriceProviderRateLimitKind,
|
||||||
requests: std::option::Option<u32>,
|
requests: std::option::Option<u32>,
|
||||||
scope: crate::PriceProviderRateLimitScope,
|
scope: crate::MarketPriceProviderRateLimitScope,
|
||||||
window_seconds: std::option::Option<u32>,
|
window_seconds: std::option::Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PriceProviderRateLimit {
|
impl MarketPriceProviderRateLimit {
|
||||||
/// Creates a validated fixed request limit.
|
/// Creates a validated fixed request limit.
|
||||||
pub fn fixed(requests: u32, window_seconds: u32, burst: std::option::Option<u32>, scope: crate::PriceProviderRateLimitScope) -> ksp_core_lib::Result<Self> {
|
pub fn fixed(
|
||||||
|
requests: u32,
|
||||||
|
window_seconds: u32,
|
||||||
|
burst: std::option::Option<u32>,
|
||||||
|
scope: crate::MarketPriceProviderRateLimitScope,
|
||||||
|
) -> ksp_core_lib::Result<Self> {
|
||||||
if requests == 0 || window_seconds == 0 || burst == std::option::Option::Some(0) {
|
if requests == 0 || window_seconds == 0 || burst == std::option::Option::Some(0) {
|
||||||
return std::result::Result::Err(provider_descriptor_error());
|
return std::result::Result::Err(provider_descriptor_error());
|
||||||
}
|
}
|
||||||
return std::result::Result::Ok(Self {
|
return std::result::Result::Ok(Self {
|
||||||
burst,
|
burst,
|
||||||
kind: crate::PriceProviderRateLimitKind::Fixed,
|
kind: crate::MarketPriceProviderRateLimitKind::Fixed,
|
||||||
requests: std::option::Option::Some(requests),
|
requests: std::option::Option::Some(requests),
|
||||||
scope,
|
scope,
|
||||||
window_seconds: std::option::Option::Some(window_seconds),
|
window_seconds: std::option::Option::Some(window_seconds),
|
||||||
@@ -103,10 +108,10 @@ impl PriceProviderRateLimit {
|
|||||||
|
|
||||||
/// Creates a dynamic/server-driven request-limit descriptor.
|
/// Creates a dynamic/server-driven request-limit descriptor.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn dynamic(scope: crate::PriceProviderRateLimitScope) -> Self {
|
pub const fn dynamic(scope: crate::MarketPriceProviderRateLimitScope) -> Self {
|
||||||
return Self {
|
return Self {
|
||||||
burst: std::option::Option::None,
|
burst: std::option::Option::None,
|
||||||
kind: crate::PriceProviderRateLimitKind::Dynamic,
|
kind: crate::MarketPriceProviderRateLimitKind::Dynamic,
|
||||||
requests: std::option::Option::None,
|
requests: std::option::Option::None,
|
||||||
scope,
|
scope,
|
||||||
window_seconds: std::option::Option::None,
|
window_seconds: std::option::Option::None,
|
||||||
@@ -121,7 +126,7 @@ impl PriceProviderRateLimit {
|
|||||||
|
|
||||||
/// Returns whether the limit is fixed or dynamic/server-driven.
|
/// Returns whether the limit is fixed or dynamic/server-driven.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn kind(&self) -> crate::PriceProviderRateLimitKind {
|
pub const fn kind(&self) -> crate::MarketPriceProviderRateLimitKind {
|
||||||
return self.kind;
|
return self.kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +138,7 @@ impl PriceProviderRateLimit {
|
|||||||
|
|
||||||
/// Returns the documented limit scope.
|
/// Returns the documented limit scope.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn scope(&self) -> crate::PriceProviderRateLimitScope {
|
pub const fn scope(&self) -> crate::MarketPriceProviderRateLimitScope {
|
||||||
return self.scope;
|
return self.scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +152,7 @@ impl PriceProviderRateLimit {
|
|||||||
/// Period used by one documented long-term provider quota.
|
/// Period used by one documented long-term provider quota.
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum PriceProviderQuotaPeriod {
|
pub enum MarketPriceProviderQuotaPeriod {
|
||||||
/// Quota resets on a provider-defined daily period.
|
/// Quota resets on a provider-defined daily period.
|
||||||
Day,
|
Day,
|
||||||
/// Quota resets on a provider-defined monthly period.
|
/// Quota resets on a provider-defined monthly period.
|
||||||
@@ -157,7 +162,7 @@ pub enum PriceProviderQuotaPeriod {
|
|||||||
/// Unit used by one documented long-term provider quota.
|
/// Unit used by one documented long-term provider quota.
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum PriceProviderQuotaUnit {
|
pub enum MarketPriceProviderQuotaUnit {
|
||||||
/// Provider-specific credits, not assumed to equal HTTP requests.
|
/// Provider-specific credits, not assumed to equal HTTP requests.
|
||||||
Credits,
|
Credits,
|
||||||
/// HTTP/API requests.
|
/// HTTP/API requests.
|
||||||
@@ -166,15 +171,15 @@ pub enum PriceProviderQuotaUnit {
|
|||||||
|
|
||||||
/// Long-term provider quota descriptor exposed as non-authoritative capability metadata.
|
/// Long-term provider quota descriptor exposed as non-authoritative capability metadata.
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize)]
|
||||||
pub struct PriceProviderLongTermQuota {
|
pub struct MarketPriceProviderLongTermQuota {
|
||||||
amount: u64,
|
amount: u64,
|
||||||
period: crate::PriceProviderQuotaPeriod,
|
period: crate::MarketPriceProviderQuotaPeriod,
|
||||||
unit: crate::PriceProviderQuotaUnit,
|
unit: crate::MarketPriceProviderQuotaUnit,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PriceProviderLongTermQuota {
|
impl MarketPriceProviderLongTermQuota {
|
||||||
/// Creates a non-zero documented quota descriptor.
|
/// Creates a non-zero documented quota descriptor.
|
||||||
pub fn new(amount: u64, period: crate::PriceProviderQuotaPeriod, unit: crate::PriceProviderQuotaUnit) -> ksp_core_lib::Result<Self> {
|
pub fn new(amount: u64, period: crate::MarketPriceProviderQuotaPeriod, unit: crate::MarketPriceProviderQuotaUnit) -> ksp_core_lib::Result<Self> {
|
||||||
if amount == 0 {
|
if amount == 0 {
|
||||||
return std::result::Result::Err(provider_descriptor_error());
|
return std::result::Result::Err(provider_descriptor_error());
|
||||||
}
|
}
|
||||||
@@ -189,13 +194,13 @@ impl PriceProviderLongTermQuota {
|
|||||||
|
|
||||||
/// Returns the provider-defined quota period.
|
/// Returns the provider-defined quota period.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn period(&self) -> crate::PriceProviderQuotaPeriod {
|
pub const fn period(&self) -> crate::MarketPriceProviderQuotaPeriod {
|
||||||
return self.period;
|
return self.period;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the documented quota unit.
|
/// Returns the documented quota unit.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn unit(&self) -> crate::PriceProviderQuotaUnit {
|
pub const fn unit(&self) -> crate::MarketPriceProviderQuotaUnit {
|
||||||
return self.unit;
|
return self.unit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,9 +208,9 @@ impl PriceProviderLongTermQuota {
|
|||||||
/// Opaque validated provider identifier owned by Off-chain Transport.
|
/// Opaque validated provider identifier owned by Off-chain Transport.
|
||||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize)]
|
||||||
#[serde(try_from = "std::string::String", into = "std::string::String")]
|
#[serde(try_from = "std::string::String", into = "std::string::String")]
|
||||||
pub struct PriceProviderId(std::string::String);
|
pub struct MarketPriceProviderId(std::string::String);
|
||||||
|
|
||||||
impl PriceProviderId {
|
impl MarketPriceProviderId {
|
||||||
/// Creates one bounded stable provider identifier.
|
/// Creates one bounded stable provider identifier.
|
||||||
pub fn new(value: impl std::convert::Into<std::string::String>) -> ksp_core_lib::Result<Self> {
|
pub fn new(value: impl std::convert::Into<std::string::String>) -> ksp_core_lib::Result<Self> {
|
||||||
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, "validating market-price provider identifier");
|
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, "validating market-price provider identifier");
|
||||||
@@ -213,7 +218,7 @@ impl PriceProviderId {
|
|||||||
if !valid_provider_id(value.as_str()) {
|
if !valid_provider_id(value.as_str()) {
|
||||||
ksp_logging_lib::warn!(target: crate::TRACING_TARGET, field = "provider_id", "rejected invalid market-price provider identifier");
|
ksp_logging_lib::warn!(target: crate::TRACING_TARGET, field = "provider_id", "rejected invalid market-price provider identifier");
|
||||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||||
crate::ERROR_CODE_PROVIDER_ID_INVALID,
|
crate::ERROR_CODE_MARKET_PRICE_PROVIDER_ID_INVALID,
|
||||||
"invalid off-chain market-price provider identifier",
|
"invalid off-chain market-price provider identifier",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -227,47 +232,47 @@ impl PriceProviderId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for PriceProviderId {
|
impl std::fmt::Display for MarketPriceProviderId {
|
||||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
return formatter.write_str(self.0.as_str());
|
return formatter.write_str(self.0.as_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::TryFrom<std::string::String> for PriceProviderId {
|
impl std::convert::TryFrom<std::string::String> for MarketPriceProviderId {
|
||||||
type Error = ksp_core_lib::Error;
|
type Error = ksp_core_lib::Error;
|
||||||
|
|
||||||
fn try_from(value: std::string::String) -> std::result::Result<Self, Self::Error> {
|
fn try_from(value: std::string::String) -> std::result::Result<Self, Self::Error> {
|
||||||
return crate::PriceProviderId::new(value);
|
return crate::MarketPriceProviderId::new(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::From<PriceProviderId> for std::string::String {
|
impl std::convert::From<MarketPriceProviderId> for std::string::String {
|
||||||
fn from(value: PriceProviderId) -> Self {
|
fn from(value: MarketPriceProviderId) -> Self {
|
||||||
return value.0;
|
return value.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provider capability and presentation descriptor consumed by provider-agnostic callers.
|
/// Provider capability and presentation descriptor consumed by provider-agnostic callers.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
|
||||||
pub struct PriceProviderDescriptor {
|
pub struct MarketPriceProviderDescriptor {
|
||||||
auth_mode: crate::PriceProviderAuthMode,
|
auth_mode: crate::MarketPriceProviderAuthMode,
|
||||||
display_name: std::string::String,
|
display_name: std::string::String,
|
||||||
id: crate::PriceProviderId,
|
id: crate::MarketPriceProviderId,
|
||||||
long_term_quota: std::option::Option<crate::PriceProviderLongTermQuota>,
|
long_term_quota: std::option::Option<crate::MarketPriceProviderLongTermQuota>,
|
||||||
rate_limit: crate::PriceProviderRateLimit,
|
rate_limit: crate::MarketPriceProviderRateLimit,
|
||||||
semantics: crate::PriceSemantics,
|
semantics: crate::MarketPriceSemantics,
|
||||||
supports_sol_usd: bool,
|
supports_sol_usd: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PriceProviderDescriptor {
|
impl MarketPriceProviderDescriptor {
|
||||||
/// Creates a validated provider-neutral descriptor.
|
/// Creates a validated provider-neutral descriptor.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
id: crate::PriceProviderId,
|
id: crate::MarketPriceProviderId,
|
||||||
display_name: impl std::convert::Into<std::string::String>,
|
display_name: impl std::convert::Into<std::string::String>,
|
||||||
semantics: crate::PriceSemantics,
|
semantics: crate::MarketPriceSemantics,
|
||||||
auth_mode: crate::PriceProviderAuthMode,
|
auth_mode: crate::MarketPriceProviderAuthMode,
|
||||||
rate_limit: crate::PriceProviderRateLimit,
|
rate_limit: crate::MarketPriceProviderRateLimit,
|
||||||
long_term_quota: std::option::Option<crate::PriceProviderLongTermQuota>,
|
long_term_quota: std::option::Option<crate::MarketPriceProviderLongTermQuota>,
|
||||||
supports_sol_usd: bool,
|
supports_sol_usd: bool,
|
||||||
) -> ksp_core_lib::Result<Self> {
|
) -> ksp_core_lib::Result<Self> {
|
||||||
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, "validating market-price provider descriptor");
|
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, "validating market-price provider descriptor");
|
||||||
@@ -281,7 +286,7 @@ impl PriceProviderDescriptor {
|
|||||||
|
|
||||||
/// Returns the configured authentication capability.
|
/// Returns the configured authentication capability.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn auth_mode(&self) -> crate::PriceProviderAuthMode {
|
pub const fn auth_mode(&self) -> crate::MarketPriceProviderAuthMode {
|
||||||
return self.auth_mode;
|
return self.auth_mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,25 +298,25 @@ impl PriceProviderDescriptor {
|
|||||||
|
|
||||||
/// Returns the opaque provider identifier.
|
/// Returns the opaque provider identifier.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn id(&self) -> &crate::PriceProviderId {
|
pub const fn id(&self) -> &crate::MarketPriceProviderId {
|
||||||
return &self.id;
|
return &self.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns optional long-term quota metadata without exposing a local remaining counter.
|
/// Returns optional long-term quota metadata without exposing a local remaining counter.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn long_term_quota(&self) -> std::option::Option<crate::PriceProviderLongTermQuota> {
|
pub const fn long_term_quota(&self) -> std::option::Option<crate::MarketPriceProviderLongTermQuota> {
|
||||||
return self.long_term_quota;
|
return self.long_term_quota;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the configured request-limit capability.
|
/// Returns the configured request-limit capability.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn rate_limit(&self) -> crate::PriceProviderRateLimit {
|
pub const fn rate_limit(&self) -> crate::MarketPriceProviderRateLimit {
|
||||||
return self.rate_limit;
|
return self.rate_limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the documented price semantics.
|
/// Returns the documented price semantics.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn semantics(&self) -> crate::PriceSemantics {
|
pub const fn semantics(&self) -> crate::MarketPriceSemantics {
|
||||||
return self.semantics;
|
return self.semantics;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,13 +330,13 @@ impl PriceProviderDescriptor {
|
|||||||
/// Generic runtime availability state exposed without provider-specific error parsing.
|
/// Generic runtime availability state exposed without provider-specific error parsing.
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
#[serde(tag = "state", rename_all = "snake_case")]
|
#[serde(tag = "state", rename_all = "snake_case")]
|
||||||
pub enum PriceProviderAvailability {
|
pub enum MarketPriceProviderAvailability {
|
||||||
/// Required authentication material is unavailable or rejected.
|
/// Required authentication material is unavailable or rejected.
|
||||||
AuthenticationUnavailable,
|
AuthenticationUnavailable,
|
||||||
/// Provider is locally cooling down until the supplied timestamp.
|
/// Provider is locally cooling down until the supplied timestamp.
|
||||||
CoolingDown {
|
CoolingDown {
|
||||||
/// Earliest known wall-clock timestamp at which a new attempt may be admitted.
|
/// Earliest known wall-clock timestamp at which a new attempt may be admitted.
|
||||||
retry_at: crate::PriceTimestamp,
|
retry_at: crate::MarketPriceTimestamp,
|
||||||
},
|
},
|
||||||
/// Provider is disabled by runtime configuration.
|
/// Provider is disabled by runtime configuration.
|
||||||
Disabled,
|
Disabled,
|
||||||
@@ -344,43 +349,43 @@ pub enum PriceProviderAvailability {
|
|||||||
/// Transport/provider failure is transient; retry time is present only when actually known.
|
/// Transport/provider failure is transient; retry time is present only when actually known.
|
||||||
TemporarilyUnavailable {
|
TemporarilyUnavailable {
|
||||||
/// Optional next retry timestamp derived from safe runtime/provider information.
|
/// Optional next retry timestamp derived from safe runtime/provider information.
|
||||||
retry_at: std::option::Option<crate::PriceTimestamp>,
|
retry_at: std::option::Option<crate::MarketPriceTimestamp>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Current provider-neutral runtime state projection.
|
/// Current provider-neutral runtime state projection.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
|
||||||
pub struct PriceProviderState {
|
pub struct MarketPriceProviderState {
|
||||||
availability: crate::PriceProviderAvailability,
|
availability: crate::MarketPriceProviderAvailability,
|
||||||
provider_id: crate::PriceProviderId,
|
provider_id: crate::MarketPriceProviderId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PriceProviderState {
|
impl MarketPriceProviderState {
|
||||||
/// Creates one generic state projection for a configured provider.
|
/// Creates one generic state projection for a configured provider.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(provider_id: crate::PriceProviderId, availability: crate::PriceProviderAvailability) -> Self {
|
pub fn new(provider_id: crate::MarketPriceProviderId, availability: crate::MarketPriceProviderAvailability) -> Self {
|
||||||
return Self { availability, provider_id };
|
return Self { availability, provider_id };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the generic availability classification.
|
/// Returns the generic availability classification.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn availability(&self) -> crate::PriceProviderAvailability {
|
pub const fn availability(&self) -> crate::MarketPriceProviderAvailability {
|
||||||
return self.availability;
|
return self.availability;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the opaque provider identifier.
|
/// Returns the opaque provider identifier.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn provider_id(&self) -> &crate::PriceProviderId {
|
pub const fn provider_id(&self) -> &crate::MarketPriceProviderId {
|
||||||
return &self.provider_id;
|
return &self.provider_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn provider_descriptor_error() -> ksp_core_lib::Error {
|
fn provider_descriptor_error() -> ksp_core_lib::Error {
|
||||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_PROVIDER_DESCRIPTOR_INVALID, "invalid off-chain market-price provider descriptor");
|
return ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_PROVIDER_DESCRIPTOR_INVALID, "invalid off-chain market-price provider descriptor");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn valid_display_name(value: &str) -> bool {
|
fn valid_display_name(value: &str) -> bool {
|
||||||
if value.is_empty() || value.len() > crate::PRICE_PROVIDER_DISPLAY_NAME_MAX_BYTES || value.trim() != value {
|
if value.is_empty() || value.len() > crate::MARKET_PRICE_PROVIDER_DISPLAY_NAME_MAX_BYTES || value.trim() != value {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for character in value.chars() {
|
for character in value.chars() {
|
||||||
@@ -392,7 +397,7 @@ fn valid_display_name(value: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn valid_provider_id(value: &str) -> bool {
|
fn valid_provider_id(value: &str) -> bool {
|
||||||
if value.is_empty() || value.len() > crate::PRICE_PROVIDER_ID_MAX_BYTES {
|
if value.is_empty() || value.len() > crate::MARKET_PRICE_PROVIDER_ID_MAX_BYTES {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for byte in value.bytes() {
|
for byte in value.bytes() {
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
// file: crates/ksp-offchain-transport-lib/src/market_price_settings.rs
|
// file: crates/ksp-offchain-transport-lib/src/market_price_settings.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
/// Common provider settings embedded by future provider-specific runtime settings.
|
/// Common provider settings embedded by future provider-specific runtime settings.
|
||||||
///
|
///
|
||||||
/// Provider-specific credentials, pair selectors and access modes intentionally do not live here because providers without those capabilities must not be
|
/// Provider-specific credentials, pair selectors and access modes intentionally do not live here because providers without those capabilities must not be
|
||||||
/// forced into artificial fields.
|
/// forced into artificial fields.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
|
||||||
pub struct PriceProviderCommonSettings {
|
pub struct MarketPriceProviderCommonSettings {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
provider_id: crate::PriceProviderId,
|
provider_id: crate::MarketPriceProviderId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PriceProviderCommonSettings {
|
impl MarketPriceProviderCommonSettings {
|
||||||
/// Creates common settings for one uniquely identified runtime provider instance.
|
/// Creates common settings for one uniquely identified runtime provider instance.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(provider_id: crate::PriceProviderId, enabled: bool) -> Self {
|
pub fn new(provider_id: crate::MarketPriceProviderId, enabled: bool) -> Self {
|
||||||
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, enabled = enabled, "constructed common market-price provider settings");
|
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, enabled = enabled, "constructed common market-price provider settings");
|
||||||
return Self { enabled, provider_id };
|
return Self { enabled, provider_id };
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ impl PriceProviderCommonSettings {
|
|||||||
|
|
||||||
/// Returns the opaque runtime provider identifier.
|
/// Returns the opaque runtime provider identifier.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn provider_id(&self) -> &crate::PriceProviderId {
|
pub const fn provider_id(&self) -> &crate::MarketPriceProviderId {
|
||||||
return &self.provider_id;
|
return &self.provider_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-offchain-transport-lib/tests/public_api.rs
|
// file: crates/ksp-offchain-transport-lib/tests/public_api.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -8,34 +8,34 @@
|
|||||||
//! Public API canaries for the first Off-chain Transport market-price capability family.
|
//! Public API canaries for the first Off-chain Transport market-price capability family.
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn public_pre_002_price_foundation_is_available_from_crate_root() -> ksp_core_lib::Result<()> {
|
fn public_pre_002_market_price_foundation_is_available_from_crate_root() -> ksp_core_lib::Result<()> {
|
||||||
assert_eq!(ksp_offchain_transport_lib::PricePair::SolUsd.code(), "SOL/USD");
|
assert_eq!(ksp_offchain_transport_lib::MarketPricePair::SolUsd.code(), "SOL/USD");
|
||||||
assert_eq!(ksp_offchain_transport_lib::PRICE_DECIMAL_MAX_SCALE, 18);
|
assert_eq!(ksp_offchain_transport_lib::MARKET_PRICE_DECIMAL_MAX_SCALE, 18);
|
||||||
let price = match ksp_offchain_transport_lib::PriceDecimal::parse("201.2500") {
|
let price = match ksp_offchain_transport_lib::MarketPriceDecimal::parse("201.2500") {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
assert_eq!(price.to_canonical_string(), "201.25");
|
assert_eq!(price.to_canonical_string(), "201.25");
|
||||||
let id = match ksp_offchain_transport_lib::PriceProviderId::new("provider-canary") {
|
let id = match ksp_offchain_transport_lib::MarketPriceProviderId::new("provider-canary") {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let common = ksp_offchain_transport_lib::PriceProviderCommonSettings::new(id.clone(), true);
|
let common = ksp_offchain_transport_lib::MarketPriceProviderCommonSettings::new(id.clone(), true);
|
||||||
assert_eq!(common.provider_id(), &id);
|
assert_eq!(common.provider_id(), &id);
|
||||||
let rate_limit = match ksp_offchain_transport_lib::PriceProviderRateLimit::fixed(
|
let rate_limit = match ksp_offchain_transport_lib::MarketPriceProviderRateLimit::fixed(
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
std::option::Option::None,
|
std::option::Option::None,
|
||||||
ksp_offchain_transport_lib::PriceProviderRateLimitScope::Ip,
|
ksp_offchain_transport_lib::MarketPriceProviderRateLimitScope::Ip,
|
||||||
) {
|
) {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let descriptor = match ksp_offchain_transport_lib::PriceProviderDescriptor::new(
|
let descriptor = match ksp_offchain_transport_lib::MarketPriceProviderDescriptor::new(
|
||||||
id.clone(),
|
id.clone(),
|
||||||
"Provider Canary",
|
"Provider Canary",
|
||||||
ksp_offchain_transport_lib::PriceSemantics::AggregatedMarket,
|
ksp_offchain_transport_lib::MarketPriceSemantics::AggregatedMarket,
|
||||||
ksp_offchain_transport_lib::PriceProviderAuthMode::None,
|
ksp_offchain_transport_lib::MarketPriceProviderAuthMode::None,
|
||||||
rate_limit,
|
rate_limit,
|
||||||
std::option::Option::None,
|
std::option::Option::None,
|
||||||
true,
|
true,
|
||||||
@@ -44,15 +44,15 @@ fn public_pre_002_price_foundation_is_available_from_crate_root() -> ksp_core_li
|
|||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
assert!(descriptor.supports_sol_usd());
|
assert!(descriptor.supports_sol_usd());
|
||||||
let provenance = match ksp_offchain_transport_lib::PriceProvenance::new("canary") {
|
let provenance = match ksp_offchain_transport_lib::MarketPriceProvenance::new("canary") {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let timestamp = ksp_offchain_transport_lib::PriceTimestamp::from_unix_millis(1);
|
let timestamp = ksp_offchain_transport_lib::MarketPriceTimestamp::from_unix_millis(1);
|
||||||
let observation = match ksp_offchain_transport_lib::SolUsdPriceObservation::new(
|
let observation = match ksp_offchain_transport_lib::MarketPriceObservation::new(
|
||||||
id,
|
id,
|
||||||
price,
|
price,
|
||||||
ksp_offchain_transport_lib::PriceSemantics::AggregatedMarket,
|
ksp_offchain_transport_lib::MarketPriceSemantics::AggregatedMarket,
|
||||||
timestamp,
|
timestamp,
|
||||||
timestamp,
|
timestamp,
|
||||||
std::option::Option::None,
|
std::option::Option::None,
|
||||||
@@ -68,11 +68,11 @@ fn public_pre_002_price_foundation_is_available_from_crate_root() -> ksp_core_li
|
|||||||
#[test]
|
#[test]
|
||||||
fn offchain_error_codes_use_owned_domain() {
|
fn offchain_error_codes_use_owned_domain() {
|
||||||
let codes = [
|
let codes = [
|
||||||
ksp_offchain_transport_lib::ERROR_CODE_PRICE_DECIMAL_INVALID,
|
ksp_offchain_transport_lib::ERROR_CODE_MARKET_PRICE_DECIMAL_INVALID,
|
||||||
ksp_offchain_transport_lib::ERROR_CODE_PROVIDER_DESCRIPTOR_INVALID,
|
ksp_offchain_transport_lib::ERROR_CODE_MARKET_PRICE_PROVIDER_DESCRIPTOR_INVALID,
|
||||||
ksp_offchain_transport_lib::ERROR_CODE_PROVIDER_ID_INVALID,
|
ksp_offchain_transport_lib::ERROR_CODE_MARKET_PRICE_PROVIDER_ID_INVALID,
|
||||||
ksp_offchain_transport_lib::ERROR_CODE_PROVIDER_OBSERVATION_INVALID,
|
ksp_offchain_transport_lib::ERROR_CODE_MARKET_PRICE_OBSERVATION_INVALID,
|
||||||
ksp_offchain_transport_lib::ERROR_CODE_PROVIDER_SETTINGS_INVALID,
|
ksp_offchain_transport_lib::ERROR_CODE_MARKET_PRICE_PROVIDER_SETTINGS_INVALID,
|
||||||
];
|
];
|
||||||
for code in codes {
|
for code in codes {
|
||||||
assert_eq!(code.domain(), "offchain_transport");
|
assert_eq!(code.domain(), "offchain_transport");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-offchain-transport-lib/unit_tests/market_price_decimal.rs
|
// file: crates/ksp-offchain-transport-lib/unit_tests/market_price_decimal.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn decimal_normalizes_fractional_and_scientific_forms_without_f64() -> ksp_core_lib::Result<()> {
|
fn decimal_normalizes_fractional_and_scientific_forms_without_f64() -> ksp_core_lib::Result<()> {
|
||||||
@@ -11,7 +11,7 @@ fn decimal_normalizes_fractional_and_scientific_forms_without_f64() -> ksp_core_
|
|||||||
("1e-3", "0.001", 1, 3),
|
("1e-3", "0.001", 1, 3),
|
||||||
];
|
];
|
||||||
for (source, expected, coefficient, scale) in cases {
|
for (source, expected, coefficient, scale) in cases {
|
||||||
let value = match crate::PriceDecimal::parse(source) {
|
let value = match crate::MarketPriceDecimal::parse(source) {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
@@ -26,34 +26,34 @@ fn decimal_normalizes_fractional_and_scientific_forms_without_f64() -> ksp_core_
|
|||||||
fn decimal_rejects_zero_negative_nonfinite_excessive_scale_and_overflow() {
|
fn decimal_rejects_zero_negative_nonfinite_excessive_scale_and_overflow() {
|
||||||
let invalid = ["0", "0.000", "-1", "+1", "NaN", "inf", "1e-19", "1e129", "340282366920938463463374607431768211456", " 1", "1 ", ".1", "1."];
|
let invalid = ["0", "0.000", "-1", "+1", "NaN", "inf", "1e-19", "1e129", "340282366920938463463374607431768211456", " 1", "1 ", ".1", "1."];
|
||||||
for source in invalid {
|
for source in invalid {
|
||||||
let result = crate::PriceDecimal::parse(source);
|
let result = crate::MarketPriceDecimal::parse(source);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
if let std::result::Result::Err(error) = result {
|
if let std::result::Result::Err(error) = result {
|
||||||
assert_eq!(error.code(), crate::ERROR_CODE_PRICE_DECIMAL_INVALID);
|
assert_eq!(error.code(), crate::ERROR_CODE_MARKET_PRICE_DECIMAL_INVALID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn decimal_serde_is_canonical_string_and_round_trips_exactly() -> ksp_core_lib::Result<()> {
|
fn decimal_serde_is_canonical_string_and_round_trips_exactly() -> ksp_core_lib::Result<()> {
|
||||||
let value = match crate::PriceDecimal::parse("123.4500") {
|
let value = match crate::MarketPriceDecimal::parse("123.4500") {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let encoded = match serde_json::to_string(&value) {
|
let encoded = match serde_json::to_string(&value) {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(_) => {
|
std::result::Result::Err(_) => {
|
||||||
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_PRICE_DECIMAL_INVALID, "test serialization failed"));
|
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_DECIMAL_INVALID, "test serialization failed"));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert_eq!(encoded, "\"123.45\"");
|
assert_eq!(encoded, "\"123.45\"");
|
||||||
let decoded: crate::PriceDecimal = match serde_json::from_str(encoded.as_str()) {
|
let decoded: crate::MarketPriceDecimal = match serde_json::from_str(encoded.as_str()) {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(_) => {
|
std::result::Result::Err(_) => {
|
||||||
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_PRICE_DECIMAL_INVALID, "test deserialization failed"));
|
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_DECIMAL_INVALID, "test deserialization failed"));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert_eq!(decoded, value);
|
assert_eq!(decoded, value);
|
||||||
assert!(serde_json::from_str::<crate::PriceDecimal>("123.45").is_err());
|
assert!(serde_json::from_str::<crate::MarketPriceDecimal>("123.45").is_err());
|
||||||
return std::result::Result::Ok(());
|
return std::result::Result::Ok(());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
// file: crates/ksp-offchain-transport-lib/unit_tests/market_price_observation.rs
|
// file: crates/ksp-offchain-transport-lib/unit_tests/market_price_observation.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn observation_preserves_pair_exact_price_semantics_timestamps_and_safe_provenance() -> ksp_core_lib::Result<()> {
|
fn observation_preserves_pair_exact_price_semantics_timestamps_and_safe_provenance() -> ksp_core_lib::Result<()> {
|
||||||
let provider_id = match crate::PriceProviderId::new("kraken") {
|
let provider_id = match crate::MarketPriceProviderId::new("kraken") {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let price = match crate::PriceDecimal::parse("204.125") {
|
let price = match crate::MarketPriceDecimal::parse("204.125") {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let provenance = match crate::PriceProvenance::new("market=SOL/USD") {
|
let provenance = match crate::MarketPriceProvenance::new("market=SOL/USD") {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let started = crate::PriceTimestamp::from_unix_millis(10_000);
|
let started = crate::MarketPriceTimestamp::from_unix_millis(10_000);
|
||||||
let received = crate::PriceTimestamp::from_unix_millis(10_250);
|
let received = crate::MarketPriceTimestamp::from_unix_millis(10_250);
|
||||||
let observation = match crate::SolUsdPriceObservation::new(
|
let observation = match crate::MarketPriceObservation::new(
|
||||||
provider_id,
|
provider_id,
|
||||||
price,
|
price,
|
||||||
crate::PriceSemantics::ExchangeLastTrade,
|
crate::MarketPriceSemantics::ExchangeLastTrade,
|
||||||
started,
|
started,
|
||||||
received,
|
received,
|
||||||
std::option::Option::None,
|
std::option::Option::None,
|
||||||
@@ -29,10 +29,10 @@ fn observation_preserves_pair_exact_price_semantics_timestamps_and_safe_provenan
|
|||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
assert_eq!(observation.pair(), crate::PricePair::SolUsd);
|
assert_eq!(observation.pair(), crate::MarketPricePair::SolUsd);
|
||||||
assert_eq!(observation.pair().code(), "SOL/USD");
|
assert_eq!(observation.pair().code(), "SOL/USD");
|
||||||
assert_eq!(observation.price(), price);
|
assert_eq!(observation.price(), price);
|
||||||
assert_eq!(observation.semantics(), crate::PriceSemantics::ExchangeLastTrade);
|
assert_eq!(observation.semantics(), crate::MarketPriceSemantics::ExchangeLastTrade);
|
||||||
assert_eq!(observation.request_started_at(), started);
|
assert_eq!(observation.request_started_at(), started);
|
||||||
assert_eq!(observation.received_at(), received);
|
assert_eq!(observation.received_at(), received);
|
||||||
assert_eq!(observation.provider_timestamp(), std::option::Option::None);
|
assert_eq!(observation.provider_timestamp(), std::option::Option::None);
|
||||||
@@ -42,25 +42,25 @@ fn observation_preserves_pair_exact_price_semantics_timestamps_and_safe_provenan
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn observation_rejects_reversed_ksp_timestamps_and_unsafe_provenance() -> ksp_core_lib::Result<()> {
|
fn observation_rejects_reversed_ksp_timestamps_and_unsafe_provenance() -> ksp_core_lib::Result<()> {
|
||||||
assert!(crate::PriceProvenance::new("line\nbreak").is_err());
|
assert!(crate::MarketPriceProvenance::new("line\nbreak").is_err());
|
||||||
let provider_id = match crate::PriceProviderId::new("coingecko") {
|
let provider_id = match crate::MarketPriceProviderId::new("coingecko") {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let price = match crate::PriceDecimal::parse("200") {
|
let price = match crate::MarketPriceDecimal::parse("200") {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let provenance = match crate::PriceProvenance::new("asset=solana") {
|
let provenance = match crate::MarketPriceProvenance::new("asset=solana") {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let result = crate::SolUsdPriceObservation::new(
|
let result = crate::MarketPriceObservation::new(
|
||||||
provider_id,
|
provider_id,
|
||||||
price,
|
price,
|
||||||
crate::PriceSemantics::AggregatedMarket,
|
crate::MarketPriceSemantics::AggregatedMarket,
|
||||||
crate::PriceTimestamp::from_unix_millis(2),
|
crate::MarketPriceTimestamp::from_unix_millis(2),
|
||||||
crate::PriceTimestamp::from_unix_millis(1),
|
crate::MarketPriceTimestamp::from_unix_millis(1),
|
||||||
std::option::Option::None,
|
std::option::Option::None,
|
||||||
provenance,
|
provenance,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,39 +1,43 @@
|
|||||||
// file: crates/ksp-offchain-transport-lib/unit_tests/market_price_provider.rs
|
// file: crates/ksp-offchain-transport-lib/unit_tests/market_price_provider.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn provider_id_is_opaque_bounded_and_stable() -> ksp_core_lib::Result<()> {
|
fn provider_id_is_opaque_bounded_and_stable() -> ksp_core_lib::Result<()> {
|
||||||
let id = match crate::PriceProviderId::new("coingecko-main") {
|
let id = match crate::MarketPriceProviderId::new("coingecko-main") {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
assert_eq!(id.as_str(), "coingecko-main");
|
assert_eq!(id.as_str(), "coingecko-main");
|
||||||
assert_eq!(id.to_string(), "coingecko-main");
|
assert_eq!(id.to_string(), "coingecko-main");
|
||||||
for invalid in ["", "CoinGecko", "coin gecko", "coin/gecko", "é"] {
|
for invalid in ["", "CoinGecko", "coin gecko", "coin/gecko", "é"] {
|
||||||
assert!(crate::PriceProviderId::new(invalid).is_err());
|
assert!(crate::MarketPriceProviderId::new(invalid).is_err());
|
||||||
}
|
}
|
||||||
return std::result::Result::Ok(());
|
return std::result::Result::Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn provider_descriptor_preserves_semantics_auth_limits_and_informational_quota() -> ksp_core_lib::Result<()> {
|
fn provider_descriptor_preserves_semantics_auth_limits_and_informational_quota() -> ksp_core_lib::Result<()> {
|
||||||
let id = match crate::PriceProviderId::new("provider-a") {
|
let id = match crate::MarketPriceProviderId::new("provider-a") {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let rate_limit = match crate::PriceProviderRateLimit::fixed(1, 2, std::option::Option::None, crate::PriceProviderRateLimitScope::Ip) {
|
let rate_limit = match crate::MarketPriceProviderRateLimit::fixed(1, 2, std::option::Option::None, crate::MarketPriceProviderRateLimitScope::Ip) {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let quota = match crate::PriceProviderLongTermQuota::new(10_000, crate::PriceProviderQuotaPeriod::Month, crate::PriceProviderQuotaUnit::Credits) {
|
let quota = match crate::MarketPriceProviderLongTermQuota::new(
|
||||||
|
10_000,
|
||||||
|
crate::MarketPriceProviderQuotaPeriod::Month,
|
||||||
|
crate::MarketPriceProviderQuotaUnit::Credits,
|
||||||
|
) {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let descriptor = match crate::PriceProviderDescriptor::new(
|
let descriptor = match crate::MarketPriceProviderDescriptor::new(
|
||||||
id,
|
id,
|
||||||
"Provider A",
|
"Provider A",
|
||||||
crate::PriceSemantics::AggregatedMarket,
|
crate::MarketPriceSemantics::AggregatedMarket,
|
||||||
crate::PriceProviderAuthMode::OptionalApiKey,
|
crate::MarketPriceProviderAuthMode::OptionalApiKey,
|
||||||
rate_limit,
|
rate_limit,
|
||||||
std::option::Option::Some(quota),
|
std::option::Option::Some(quota),
|
||||||
true,
|
true,
|
||||||
@@ -42,8 +46,8 @@ fn provider_descriptor_preserves_semantics_auth_limits_and_informational_quota()
|
|||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
assert_eq!(descriptor.display_name(), "Provider A");
|
assert_eq!(descriptor.display_name(), "Provider A");
|
||||||
assert_eq!(descriptor.semantics(), crate::PriceSemantics::AggregatedMarket);
|
assert_eq!(descriptor.semantics(), crate::MarketPriceSemantics::AggregatedMarket);
|
||||||
assert_eq!(descriptor.auth_mode(), crate::PriceProviderAuthMode::OptionalApiKey);
|
assert_eq!(descriptor.auth_mode(), crate::MarketPriceProviderAuthMode::OptionalApiKey);
|
||||||
assert_eq!(descriptor.rate_limit(), rate_limit);
|
assert_eq!(descriptor.rate_limit(), rate_limit);
|
||||||
assert_eq!(descriptor.long_term_quota(), std::option::Option::Some(quota));
|
assert_eq!(descriptor.long_term_quota(), std::option::Option::Some(quota));
|
||||||
assert!(descriptor.supports_sol_usd());
|
assert!(descriptor.supports_sol_usd());
|
||||||
@@ -52,12 +56,12 @@ fn provider_descriptor_preserves_semantics_auth_limits_and_informational_quota()
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn provider_limit_descriptors_reject_zero_and_model_dynamic_scope() {
|
fn provider_limit_descriptors_reject_zero_and_model_dynamic_scope() {
|
||||||
assert!(crate::PriceProviderRateLimit::fixed(0, 1, std::option::Option::None, crate::PriceProviderRateLimitScope::Ip).is_err());
|
assert!(crate::MarketPriceProviderRateLimit::fixed(0, 1, std::option::Option::None, crate::MarketPriceProviderRateLimitScope::Ip).is_err());
|
||||||
assert!(crate::PriceProviderRateLimit::fixed(1, 0, std::option::Option::None, crate::PriceProviderRateLimitScope::Ip).is_err());
|
assert!(crate::MarketPriceProviderRateLimit::fixed(1, 0, std::option::Option::None, crate::MarketPriceProviderRateLimitScope::Ip).is_err());
|
||||||
assert!(crate::PriceProviderRateLimit::fixed(1, 1, std::option::Option::Some(0), crate::PriceProviderRateLimitScope::Ip).is_err());
|
assert!(crate::MarketPriceProviderRateLimit::fixed(1, 1, std::option::Option::Some(0), crate::MarketPriceProviderRateLimitScope::Ip).is_err());
|
||||||
let dynamic = crate::PriceProviderRateLimit::dynamic(crate::PriceProviderRateLimitScope::Ip);
|
let dynamic = crate::MarketPriceProviderRateLimit::dynamic(crate::MarketPriceProviderRateLimitScope::Ip);
|
||||||
assert_eq!(dynamic.kind(), crate::PriceProviderRateLimitKind::Dynamic);
|
assert_eq!(dynamic.kind(), crate::MarketPriceProviderRateLimitKind::Dynamic);
|
||||||
assert_eq!(dynamic.scope(), crate::PriceProviderRateLimitScope::Ip);
|
assert_eq!(dynamic.scope(), crate::MarketPriceProviderRateLimitScope::Ip);
|
||||||
assert_eq!(dynamic.requests(), std::option::Option::None);
|
assert_eq!(dynamic.requests(), std::option::Option::None);
|
||||||
assert_eq!(dynamic.window_seconds(), std::option::Option::None);
|
assert_eq!(dynamic.window_seconds(), std::option::Option::None);
|
||||||
assert_eq!(dynamic.burst(), std::option::Option::None);
|
assert_eq!(dynamic.burst(), std::option::Option::None);
|
||||||
@@ -65,13 +69,13 @@ fn provider_limit_descriptors_reject_zero_and_model_dynamic_scope() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn provider_availability_keeps_cooldown_and_outage_distinct() -> ksp_core_lib::Result<()> {
|
fn provider_availability_keeps_cooldown_and_outage_distinct() -> ksp_core_lib::Result<()> {
|
||||||
let id = match crate::PriceProviderId::new("jupiter") {
|
let id = match crate::MarketPriceProviderId::new("jupiter") {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let retry_at = crate::PriceTimestamp::from_unix_millis(1_777_777_777_000);
|
let retry_at = crate::MarketPriceTimestamp::from_unix_millis(1_777_777_777_000);
|
||||||
let state = crate::PriceProviderState::new(id, crate::PriceProviderAvailability::CoolingDown { retry_at });
|
let state = crate::MarketPriceProviderState::new(id, crate::MarketPriceProviderAvailability::CoolingDown { retry_at });
|
||||||
assert_eq!(state.availability(), crate::PriceProviderAvailability::CoolingDown { retry_at });
|
assert_eq!(state.availability(), crate::MarketPriceProviderAvailability::CoolingDown { retry_at });
|
||||||
assert_ne!(state.availability(), crate::PriceProviderAvailability::TemporarilyUnavailable { retry_at: std::option::Option::Some(retry_at) });
|
assert_ne!(state.availability(), crate::MarketPriceProviderAvailability::TemporarilyUnavailable { retry_at: std::option::Option::Some(retry_at) });
|
||||||
return std::result::Result::Ok(());
|
return std::result::Result::Ok(());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
// file: crates/ksp-offchain-transport-lib/unit_tests/market_price_settings.rs
|
// file: crates/ksp-offchain-transport-lib/unit_tests/market_price_settings.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn common_settings_contain_only_generic_identity_and_enablement() -> ksp_core_lib::Result<()> {
|
fn common_settings_contain_only_generic_identity_and_enablement() -> ksp_core_lib::Result<()> {
|
||||||
let provider_id = match crate::PriceProviderId::new("coinpaprika") {
|
let provider_id = match crate::MarketPriceProviderId::new("coinpaprika") {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let settings = crate::PriceProviderCommonSettings::new(provider_id, true);
|
let settings = crate::MarketPriceProviderCommonSettings::new(provider_id, true);
|
||||||
assert!(settings.enabled());
|
assert!(settings.enabled());
|
||||||
assert_eq!(settings.provider_id().as_str(), "coinpaprika");
|
assert_eq!(settings.provider_id().as_str(), "coinpaprika");
|
||||||
let serialized = match serde_json::to_value(&settings) {
|
let serialized = match serde_json::to_value(&settings) {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(_) => {
|
std::result::Result::Err(_) => {
|
||||||
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_PROVIDER_SETTINGS_INVALID, "test serialization failed"));
|
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_PROVIDER_SETTINGS_INVALID, "test serialization failed"));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert!(serialized.get("api_key").is_none());
|
assert!(serialized.get("api_key").is_none());
|
||||||
|
|||||||
185
deltas/0.2.11/pre.002-fix.003.md
Normal file
185
deltas/0.2.11/pre.002-fix.003.md
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
<!-- file: deltas/0.2.11/pre.002-fix.003.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.11-pre.002-fix.003` — nommage public `MarketPrice*`
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
Ce correctif s'applique exclusivement après :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.10
|
||||||
|
+ 0.2.11-pre.001
|
||||||
|
+ 0.2.11-pre.001-fix.001
|
||||||
|
+ 0.2.11-pre.002
|
||||||
|
+ 0.2.11-pre.002-fix.001
|
||||||
|
+ 0.2.11-pre.002-fix.002
|
||||||
|
```
|
||||||
|
|
||||||
|
La version Cargo attendue avant application est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.11-pre.2.fix.2
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Motif du correctif
|
||||||
|
|
||||||
|
`pre.002-fix.001` a correctement classé la première capacité de `ksp-offchain-transport-lib` sous les modules privés `market_price_*`, mais sa façade crate-root conservait des noms publics génériques `Price*`.
|
||||||
|
|
||||||
|
Cette asymétrie créerait une dette publique dès l'apparition d'autres familles hétérogènes, notamment :
|
||||||
|
|
||||||
|
```text
|
||||||
|
swap_quote_*
|
||||||
|
metadata_*
|
||||||
|
autres données off-chain futures
|
||||||
|
```
|
||||||
|
|
||||||
|
Des noms tels que `PriceProviderId`, `PriceProviderState` ou `PriceTimestamp` pourraient alors être interprétés comme des abstractions crate-wide alors qu'ils ne sont actuellement prouvés que pour la famille `market_price`.
|
||||||
|
|
||||||
|
Le correctif applique donc la même frontière de responsabilité aux modules privés et à la façade publique avant l'introduction du runtime HTTP de `pre.003`.
|
||||||
|
|
||||||
|
## 3. Convention publique retenue
|
||||||
|
|
||||||
|
Toute API spécifique à la famille `market_price` porte désormais le préfixe :
|
||||||
|
|
||||||
|
```text
|
||||||
|
MarketPrice*
|
||||||
|
```
|
||||||
|
|
||||||
|
et toute constante correspondante :
|
||||||
|
|
||||||
|
```text
|
||||||
|
MARKET_PRICE_*
|
||||||
|
ERROR_CODE_MARKET_PRICE_*
|
||||||
|
```
|
||||||
|
|
||||||
|
Exemples principaux :
|
||||||
|
|
||||||
|
```text
|
||||||
|
PriceDecimal -> MarketPriceDecimal
|
||||||
|
SolUsdPriceObservation -> MarketPriceObservation
|
||||||
|
PriceTimestamp -> MarketPriceTimestamp
|
||||||
|
PriceProvenance -> MarketPriceProvenance
|
||||||
|
PricePair -> MarketPricePair
|
||||||
|
PriceSemantics -> MarketPriceSemantics
|
||||||
|
PriceProviderId -> MarketPriceProviderId
|
||||||
|
PriceProviderDescriptor -> MarketPriceProviderDescriptor
|
||||||
|
PriceProviderRateLimit -> MarketPriceProviderRateLimit
|
||||||
|
PriceProviderAvailability -> MarketPriceProviderAvailability
|
||||||
|
PriceProviderState -> MarketPriceProviderState
|
||||||
|
PriceProviderCommonSettings -> MarketPriceProviderCommonSettings
|
||||||
|
```
|
||||||
|
|
||||||
|
Les enums de rate-limit/quota/auth suivent la même règle.
|
||||||
|
|
||||||
|
Aucun alias public `Price*` n'est conservé. La release est encore en prerelease et conserver ces aliases figerait précisément l'ambiguïté que ce correctif doit supprimer.
|
||||||
|
|
||||||
|
## 4. Observation V1
|
||||||
|
|
||||||
|
`SolUsdPriceObservation` devient `MarketPriceObservation` plutôt que `MarketPriceSolUsdObservation`.
|
||||||
|
|
||||||
|
La paire réellement supportée reste portée explicitement par :
|
||||||
|
|
||||||
|
```text
|
||||||
|
MarketPricePair::SolUsd
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette forme évite un nouveau renommage du type observation si une version ultérieure étend proprement la famille `market_price` à d'autres paires, tout en conservant le gate V1 strictement limité à SOL/USD.
|
||||||
|
|
||||||
|
## 5. Codes d'erreur
|
||||||
|
|
||||||
|
Les identifiants publics et les codes textuels spécifiques à cette famille sont également namespacés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
market_price_decimal_invalid
|
||||||
|
market_price_provider_descriptor_invalid
|
||||||
|
market_price_provider_id_invalid
|
||||||
|
market_price_observation_invalid
|
||||||
|
market_price_provider_settings_invalid
|
||||||
|
```
|
||||||
|
|
||||||
|
Le domaine `ksp_core_lib::ErrorCode` reste :
|
||||||
|
|
||||||
|
```text
|
||||||
|
offchain_transport
|
||||||
|
```
|
||||||
|
|
||||||
|
Une future famille peut ainsi ajouter ses propres codes sans collision sémantique avec `market_price`.
|
||||||
|
|
||||||
|
## 6. Version technique
|
||||||
|
|
||||||
|
Le correctif modifie du Rust public. `VER-ID-007` et `VER-ID-010` imposent donc :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = 0.2.11-pre.2.fix.3
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Validation opérateur acquise avant ce fix
|
||||||
|
|
||||||
|
L'état `0.2.11-pre.002-fix.002` a été validé par l'opérateur le `2026-08-25` avec :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all exécuté
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py clean
|
||||||
|
python3 scripts/audit_markdown_tables.py clean, 116 tables / 104 files
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo test -p ksp-offchain-transport-lib PASS, 10 unit + 1 boundary + 2 public API
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
`cargo test --workspace` n'a pas été rejoué après `pre.002-fix.002` et n'est donc pas déclaré PASS pour cet état exact.
|
||||||
|
|
||||||
|
## 8. Validations demandées après application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.2.11
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-offchain-transport-lib
|
||||||
|
cargo test --workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
Le canari public doit notamment compiler exclusivement avec les noms `MarketPrice*` et ne plus trouver de surface publique `Price*` historique.
|
||||||
|
|
||||||
|
## 9. Scope inchangé
|
||||||
|
|
||||||
|
Ce correctif reste strictement rattaché à la fondation `pre.002` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun reqwest
|
||||||
|
aucun client HTTP
|
||||||
|
aucun limiter actif
|
||||||
|
aucun adapter provider
|
||||||
|
aucun credential provider
|
||||||
|
aucune nouvelle paire
|
||||||
|
aucune quote Jupiter
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.003` reste propriétaire du premier runtime HTTP commun.
|
||||||
|
|
||||||
|
## 10. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-offchain-transport-lib/src/error.rs
|
||||||
|
crates/ksp-offchain-transport-lib/src/lib.rs
|
||||||
|
crates/ksp-offchain-transport-lib/src/market_price_decimal.rs
|
||||||
|
crates/ksp-offchain-transport-lib/src/market_price_observation.rs
|
||||||
|
crates/ksp-offchain-transport-lib/src/market_price_provider.rs
|
||||||
|
crates/ksp-offchain-transport-lib/src/market_price_settings.rs
|
||||||
|
crates/ksp-offchain-transport-lib/tests/public_api.rs
|
||||||
|
crates/ksp-offchain-transport-lib/unit_tests/market_price_decimal.rs
|
||||||
|
crates/ksp-offchain-transport-lib/unit_tests/market_price_observation.rs
|
||||||
|
crates/ksp-offchain-transport-lib/unit_tests/market_price_provider.rs
|
||||||
|
crates/ksp-offchain-transport-lib/unit_tests/market_price_settings.rs
|
||||||
|
docs/plans/018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md
|
||||||
|
docs/validation/014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Fichier ajouté
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.2.11/pre.002-fix.003.md
|
||||||
|
```
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<!-- file: docs/plans/018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md -->
|
<!-- file: docs/plans/018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md -->
|
||||||
<!-- version: 5 -->
|
<!-- version: 6 -->
|
||||||
|
|
||||||
# Plan `0.2.11` — Off-chain price transport SOL/USD multi-provider
|
# Plan `0.2.11` — Off-chain price transport SOL/USD multi-provider
|
||||||
|
|
||||||
**Statut courant : `0.2.11-pre.002-fix.002` ferme le dernier écart Clippy de la fondation avant `pre.003`. Les rustdocs, la façade Logging KSP et la taxonomie interne `market_price_*` restent acquises ; le contrat V1 reste limité à SOL/USD et aucun client HTTP/provider n'est encore ajouté.**
|
**Statut courant : `0.2.11-pre.002-fix.003` ferme le nommage public de la famille `market_price` avant `pre.003`. Les types, constantes et codes d'erreur publics spécifiques à cette famille portent désormais le préfixe `MarketPrice` / `MARKET_PRICE`, afin d'éviter une collision future avec `swap_quote_*` ou une autre capacité off-chain. Le contrat V1 reste limité à SOL/USD et aucun client HTTP/provider n'est encore ajouté.**
|
||||||
|
|
||||||
## 1. Base et autorité
|
## 1. Base et autorité
|
||||||
|
|
||||||
@@ -110,6 +110,8 @@ swap_quote_* réservé aux futures quotes montant/route, uniq
|
|||||||
|
|
||||||
Les fichiers génériques `decimal.rs`, `observation.rs`, `provider.rs` et `settings.rs` introduits initialement par `pre.002` sont donc corrigés avant toute extension runtime : la fonctionnalité actuelle est préfixée `market_price_*`. Les futurs domaines ne doivent pas être ajoutés dans ces modules uniquement parce qu'ils utilisent eux aussi un provider HTTP.
|
Les fichiers génériques `decimal.rs`, `observation.rs`, `provider.rs` et `settings.rs` introduits initialement par `pre.002` sont donc corrigés avant toute extension runtime : la fonctionnalité actuelle est préfixée `market_price_*`. Les futurs domaines ne doivent pas être ajoutés dans ces modules uniquement parce qu'ils utilisent eux aussi un provider HTTP.
|
||||||
|
|
||||||
|
La même frontière s'applique à la **façade publique**. À partir de `pre.002-fix.003`, tout symbole public spécifique à cette famille porte un nom explicitement `MarketPrice*` ou `MARKET_PRICE_*` : `MarketPriceDecimal`, `MarketPriceObservation`, `MarketPriceProviderId`, `MarketPriceProviderDescriptor`, `MarketPriceProviderRateLimit`, etc. Aucun alias public `Price*` n'est conservé pendant cette prerelease, car il figerait précisément l'ambiguïté que le correctif supprime. Une future famille `swap_quote_*` devra employer son propre préfixe public `SwapQuote*`; une abstraction sans préfixe de famille ne pourra être créée que lorsqu'un invariant réellement partagé aura été démontré.
|
||||||
|
|
||||||
La règle est **responsabilité avant taille** : un module commun n'est promu au niveau crate-wide que lorsqu'au moins deux familles réelles partagent le même invariant. Ainsi, le HTTP, le scheduling ou une identité provider pourront être factorisés plus tard si leur partage est effectivement démontré ; aucune abstraction générique n'est créée aujourd'hui uniquement pour anticiper Jupiter Quotes.
|
La règle est **responsabilité avant taille** : un module commun n'est promu au niveau crate-wide que lorsqu'au moins deux familles réelles partagent le même invariant. Ainsi, le HTTP, le scheduling ou une identité provider pourront être factorisés plus tard si leur partage est effectivement démontré ; aucune abstraction générique n'est créée aujourd'hui uniquement pour anticiper Jupiter Quotes.
|
||||||
|
|
||||||
### 3.2 Lecture, écriture et verbes HTTP
|
### 3.2 Lecture, écriture et verbes HTTP
|
||||||
@@ -339,7 +341,7 @@ Le choix d'une paire explicite évite d'introduire silencieusement une politique
|
|||||||
|
|
||||||
Le gate rejette `f64` comme représentation canonique publique du prix.
|
Le gate rejette `f64` comme représentation canonique publique du prix.
|
||||||
|
|
||||||
Décision matérialisée en `pre.002` : `PriceDecimal` est le type décimal KSP borné, sérialisable sans perte et construit depuis la représentation textuelle du provider.
|
Décision matérialisée en `pre.002` : `MarketPriceDecimal` est le type décimal KSP borné, sérialisable sans perte et construit depuis la représentation textuelle du provider.
|
||||||
|
|
||||||
Contrat V1 exact :
|
Contrat V1 exact :
|
||||||
|
|
||||||
@@ -358,7 +360,7 @@ serialization canonique décimale en chaîne
|
|||||||
|
|
||||||
Le wire d'un provider peut être JSON number ou string. L'adapter le convertit vers le type KSP sans utiliser `as_f64()` comme vérité canonique.
|
Le wire d'un provider peut être JSON number ou string. L'adapter le convertit vers le type KSP sans utiliser `as_f64()` comme vérité canonique.
|
||||||
|
|
||||||
`PriceDecimal` sérialise toujours sa valeur canonique sous forme de chaîne décimale non scientifique. Le zéro est invalide pour ce type puisqu'il représente exclusivement un prix réussi ; l'absence de prix reste donc hors de la valeur numérique elle-même.
|
`MarketPriceDecimal` sérialise toujours sa valeur canonique sous forme de chaîne décimale non scientifique. Le zéro est invalide pour ce type puisqu'il représente exclusivement un prix réussi ; l'absence de prix reste donc hors de la valeur numérique elle-même.
|
||||||
|
|
||||||
Cette solution évite d'ajouter une crate decimal uniquement pour V1 et prépare le passage frontend sans perte IEEE-754.
|
Cette solution évite d'ajouter une crate decimal uniquement pour V1 et prépare le passage frontend sans perte IEEE-754.
|
||||||
|
|
||||||
@@ -376,7 +378,7 @@ instant provider optionnel lorsqu'il existe réellement
|
|||||||
provenance provider sûre et bornée
|
provenance provider sûre et bornée
|
||||||
```
|
```
|
||||||
|
|
||||||
`pre.002` matérialise ce contrat avec `SolUsdPriceObservation`, `PriceTimestamp` (millisecondes UTC depuis Unix epoch) et `PriceProvenance` bornée à 256 octets sans caractères de contrôle. Le constructeur d'observation rejette un instant de réception antérieur au départ de requête.
|
`pre.002` matérialise ce contrat avec `MarketPriceObservation`, `MarketPriceTimestamp` (millisecondes UTC depuis Unix epoch) et `MarketPriceProvenance` bornée à 256 octets sans caractères de contrôle. Le constructeur d'observation rejette un instant de réception antérieur au départ de requête.
|
||||||
|
|
||||||
Règles :
|
Règles :
|
||||||
|
|
||||||
@@ -410,7 +412,7 @@ limitation générique
|
|||||||
support SOL/USD
|
support SOL/USD
|
||||||
```
|
```
|
||||||
|
|
||||||
`pre.002` fixe `PriceProviderId`, `PriceProviderDescriptor`, `PriceProviderAuthMode`, `PriceProviderRateLimit`, `PriceProviderLongTermQuota` et leurs enums de scope/période/unité. Les limites fixes exigent un budget et une fenêtre non nuls ; les limites dynamiques restent explicitement distinctes. Les quotas longs termes sont descriptifs et ne deviennent jamais un compteur local de quota restant.
|
`pre.002` fixe `MarketPriceProviderId`, `MarketPriceProviderDescriptor`, `MarketPriceProviderAuthMode`, `MarketPriceProviderRateLimit`, `MarketPriceProviderLongTermQuota` et leurs enums de scope/période/unité. Les limites fixes exigent un budget et une fenêtre non nuls ; les limites dynamiques restent explicitement distinctes. Les quotas longs termes sont descriptifs et ne deviennent jamais un compteur local de quota restant.
|
||||||
|
|
||||||
L'état runtime peut représenter au minimum :
|
L'état runtime peut représenter au minimum :
|
||||||
|
|
||||||
@@ -424,7 +426,7 @@ misconfigured
|
|||||||
disabled
|
disabled
|
||||||
```
|
```
|
||||||
|
|
||||||
`PriceProviderAvailability` et `PriceProviderState` matérialisent cette projection générique dès `pre.002`; le registry et les transitions runtime effectives restent prévus en `pre.007`.
|
`MarketPriceProviderAvailability` et `MarketPriceProviderState` matérialisent cette projection générique dès `pre.002`; le registry et les transitions runtime effectives restent prévus en `pre.007`.
|
||||||
|
|
||||||
Les noms Rust de cette fondation sont désormais matérialisés ; aucun consumer ne doit parser des strings d'erreur pour connaître cet état.
|
Les noms Rust de cette fondation sont désormais matérialisés ; aucun consumer ne doit parser des strings d'erreur pour connaître cet état.
|
||||||
|
|
||||||
@@ -662,7 +664,7 @@ Remplacement du forecast tabulaire par des sous-sections éditables, clarificati
|
|||||||
|
|
||||||
**Statut : réalisé.**
|
**Statut : réalisé.**
|
||||||
|
|
||||||
Création de la crate et de sa façade publique initiale : `PriceDecimal`, paire SOL/USD, observation/provenance/timestamps, identifiants/descriptors provider, capacités auth/rate-limit/quota, settings communs minimaux et états d'availability provider-neutral. Aucun HTTP, limiter actif, credential provider ou adapter wire n'est avancé depuis `pre.003+`.
|
Création de la crate et de sa façade publique initiale : `MarketPriceDecimal`, paire SOL/USD, observation/provenance/timestamps, identifiants/descriptors provider, capacités auth/rate-limit/quota, settings communs minimaux et états d'availability provider-neutral. Aucun HTTP, limiter actif, credential provider ou adapter wire n'est avancé depuis `pre.003+`.
|
||||||
|
|
||||||
#### `pre.002-fix.001` — Rustdoc, Logging et taxonomie Off-chain
|
#### `pre.002-fix.001` — Rustdoc, Logging et taxonomie Off-chain
|
||||||
|
|
||||||
@@ -676,6 +678,12 @@ Correction des deux warnings `missing_docs` des tests d'intégration, ajout de `
|
|||||||
|
|
||||||
Correction du canari `dependency_boundary` pour respecter `-D clippy::implicit-return`, sans changement de contrat ni de comportement runtime. La version technique workspace devient `0.2.11-pre.2.fix.2`.
|
Correction du canari `dependency_boundary` pour respecter `-D clippy::implicit-return`, sans changement de contrat ni de comportement runtime. La version technique workspace devient `0.2.11-pre.2.fix.2`.
|
||||||
|
|
||||||
|
#### `pre.002-fix.003` — Nommage public `MarketPrice*`
|
||||||
|
|
||||||
|
**Statut : réalisé.**
|
||||||
|
|
||||||
|
Préfixage de toute la surface publique spécifique à la famille `market_price` en `MarketPrice*` / `MARKET_PRICE_*`, y compris observation, décimal, timestamps, provenance, provider settings/descriptors/limits/availability et codes d'erreur. Aucun alias `Price*` n'est conservé : le correctif évite de devoir renommer ces API lorsque d'autres familles telles que `swap_quote_*` seront ajoutées. La version technique workspace devient `0.2.11-pre.2.fix.3`.
|
||||||
|
|
||||||
### `pre.003` — HTTP REST commun et rate limiting
|
### `pre.003` — HTTP REST commun et rate limiting
|
||||||
|
|
||||||
**Statut : planifié.**
|
**Statut : planifié.**
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/validation/014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md -->
|
<!-- file: docs/validation/014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md -->
|
||||||
<!-- version: 4 -->
|
<!-- version: 5 -->
|
||||||
|
|
||||||
# Validation `0.2.11` — Off-chain price transport SOL/USD
|
# Validation `0.2.11` — Off-chain price transport SOL/USD
|
||||||
|
|
||||||
@@ -46,19 +46,19 @@ Les statuts `PLANNED` restent non validés tant que la tranche correspondante n'
|
|||||||
|
|
||||||
## 3. Gate `0.2.11-pre.002`
|
## 3. Gate `0.2.11-pre.002`
|
||||||
|
|
||||||
| Critère | Statut | Preuve |
|
| Critère | Statut | Preuve |
|
||||||
|----------------------------------------------------|--------|----------------------------------------------------|
|
|----------------------------------------------------|--------|----------------------------------------------------------------|
|
||||||
| crate `ksp-offchain-transport-lib` créée | PASS | membre workspace + package dédié |
|
| crate `ksp-offchain-transport-lib` créée | PASS | membre workspace + package dédié |
|
||||||
| version workspace synchronisée sur `0.2.11-pre.2` | PASS | `Cargo.toml` racine |
|
| version workspace synchronisée sur `0.2.11-pre.2` | PASS | `Cargo.toml` racine |
|
||||||
| dépendance inverse vers Config absente | PASS | manifest crate |
|
| dépendance inverse vers Config absente | PASS | manifest crate |
|
||||||
| `reqwest` et SDK providers absents de la fondation | PASS | manifest crate |
|
| `reqwest` et SDK providers absents de la fondation | PASS | manifest crate |
|
||||||
| `PriceDecimal` exact sans canon `f64` | PASS | coefficient `u128`, scale 18, sérialisation string |
|
| `MarketPriceDecimal` exact sans canon `f64` | PASS | coefficient `u128`, scale 18, sérialisation string |
|
||||||
| SOL/USD seule paire publique V1 | PASS | `PricePair::SolUsd` + `SolUsdPriceObservation` |
|
| SOL/USD seule paire publique V1 | PASS | `MarketPricePair::SolUsd` + `MarketPriceObservation` |
|
||||||
| identifiant/descripteur provider opaques | PASS | `PriceProviderId` + `PriceProviderDescriptor` |
|
| identifiant/descripteur provider opaques | PASS | `MarketPriceProviderId` + `MarketPriceProviderDescriptor` |
|
||||||
| auth/rate-limit/quota représentables génériquement | PASS | types provider-neutral dédiés |
|
| auth/rate-limit/quota représentables génériquement | PASS | types provider-neutral dédiés |
|
||||||
| settings communs sans faux endpoint/API key | PASS | `PriceProviderCommonSettings` |
|
| settings communs sans faux endpoint/API key | PASS | `MarketPriceProviderCommonSettings` |
|
||||||
| availability provider-neutral représentable | PASS | `PriceProviderAvailability` + `PriceProviderState` |
|
| availability provider-neutral représentable | PASS | `MarketPriceProviderAvailability` + `MarketPriceProviderState` |
|
||||||
| HTTP actif ou adapter provider ajouté | N/A | explicitement réservé à `pre.003+` |
|
| HTTP actif ou adapter provider ajouté | N/A | explicitement réservé à `pre.003+` |
|
||||||
|
|
||||||
## 3.1 Taxonomie Off-chain et observabilité
|
## 3.1 Taxonomie Off-chain et observabilité
|
||||||
|
|
||||||
@@ -81,6 +81,28 @@ Les statuts `PLANNED` restent non validés tant que la tranche correspondante n'
|
|||||||
|
|
||||||
Le gate `cargo clippy --workspace --all-targets` de `pre.002-fix.001` a détecté un unique écart `clippy::implicit-return` dans le canari `tests/dependency_boundary.rs`. Le correctif ajoute le `return` explicite attendu dans la closure de `Iterator::any` et synchronise `workspace.package.version` sur `0.2.11-pre.2.fix.2`. Aucun contrat public, comportement runtime ou scope provider n'est modifié.
|
Le gate `cargo clippy --workspace --all-targets` de `pre.002-fix.001` a détecté un unique écart `clippy::implicit-return` dans le canari `tests/dependency_boundary.rs`. Le correctif ajoute le `return` explicite attendu dans la closure de `Iterator::any` et synchronise `workspace.package.version` sur `0.2.11-pre.2.fix.2`. Aucun contrat public, comportement runtime ou scope provider n'est modifié.
|
||||||
|
|
||||||
|
Les validations opérateur du `2026-08-25` sur `pre.002-fix.002` sont : `cargo fmt --all` PASS, audit Rust PASS, audit Markdown PASS, `cargo check --workspace` PASS, `cargo test -p ksp-offchain-transport-lib` PASS avec 13 tests exécutés, et `cargo clippy --workspace --all-targets` PASS. `cargo test --workspace` n'a pas été rejoué après ce fix et n'est donc pas déclaré PASS pour cet état exact.
|
||||||
|
|
||||||
|
## 3.3 Correctif `0.2.11-pre.002-fix.003`
|
||||||
|
|
||||||
|
Le nommage public initial `Price*` restait trop générique alors que les modules qui possèdent ces contrats sont explicitement la famille `market_price_*`. Le correctif synchronise `workspace.package.version` sur `0.2.11-pre.2.fix.3` et renomme sans alias de compatibilité toute la surface publique spécifique à cette famille en `MarketPrice*` / `MARKET_PRICE_*`.
|
||||||
|
|
||||||
|
Le contrat attendu après application est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
MarketPriceDecimal
|
||||||
|
MarketPriceObservation
|
||||||
|
MarketPriceTimestamp
|
||||||
|
MarketPriceProvenance
|
||||||
|
MarketPricePair
|
||||||
|
MarketPriceSemantics
|
||||||
|
MarketPriceProvider*
|
||||||
|
MARKET_PRICE_*
|
||||||
|
ERROR_CODE_MARKET_PRICE_*
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette correction reste dans la responsabilité de `pre.002` : elle stabilise la fondation et sa façade avant l'introduction du HTTP runtime en `pre.003`.
|
||||||
|
|
||||||
## 4. Matrice provider prévue
|
## 4. Matrice provider prévue
|
||||||
|
|
||||||
| Provider | SOL/USD V1 | Gratuit V1 | Mode auth prévu | Test déterministe | Smoke live | Statut courant |
|
| Provider | SOL/USD V1 | Gratuit V1 | Mode auth prévu | Test déterministe | Smoke live | Statut courant |
|
||||||
|
|||||||
Reference in New Issue
Block a user