v0.2.11-pre.011

This commit is contained in:
2026-08-26 14:37:59 +02:00
parent 98708df6e0
commit 251f9f9ad8
5 changed files with 461 additions and 46 deletions

View File

@@ -0,0 +1,180 @@
// file: crates/ksp-offchain-transport-lib/tests/market_price_live_smoke.rs
// version: 1
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
//! Opt-in live SOL/USD smokes for the V1 Off-chain Transport market-price providers.
use std::io::IsTerminal; // rust-rules: trait-import
const DEXSCREENER_SOL_USDC_ORCA_PAIR: &str = "7qbRF6YsyGuLUVs6Y1q64bdVrfe4ZcUUz1JRdoVNUJnm";
fn smoke_error(message: &'static str) -> ksp_core_lib::Error {
return ksp_core_lib::Error::new(ksp_offchain_transport_lib::ERROR_CODE_MARKET_PRICE_REFRESH_INVALID, message);
}
fn keyless_setups() -> ksp_core_lib::Result<std::vec::Vec<ksp_offchain_transport_lib::MarketPriceProviderSetup>> {
let mut setups = std::vec::Vec::with_capacity(7);
let coinbase = match ksp_offchain_transport_lib::MarketPriceCoinbaseExchangeSettings::new(true) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinbaseExchange(coinbase));
let coingecko = match ksp_offchain_transport_lib::MarketPriceCoinGeckoSettings::keyless(true) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinGecko(coingecko));
let coinmarketcap = match ksp_offchain_transport_lib::MarketPriceCoinMarketCapSettings::keyless(true) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinMarketCap(coinmarketcap));
let coinpaprika = match ksp_offchain_transport_lib::MarketPriceCoinPaprikaSettings::new(true) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinPaprika(coinpaprika));
let dexscreener =
match ksp_offchain_transport_lib::MarketPriceDexScreenerSettings::new(true, std::option::Option::Some(DEXSCREENER_SOL_USDC_ORCA_PAIR.to_owned())) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::DexScreener(dexscreener));
let jupiter = match ksp_offchain_transport_lib::MarketPriceJupiterSettings::keyless(true) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::Jupiter(jupiter));
let kraken = match ksp_offchain_transport_lib::MarketPriceKrakenSettings::new(true) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::Kraken(kraken));
return std::result::Result::Ok(setups);
}
fn read_secret_line(label: &'static str) -> ksp_core_lib::Result<std::string::String> {
let mut secret = std::string::String::new();
match std::io::stdin().read_line(&mut secret) {
std::result::Result::Ok(_) => {},
std::result::Result::Err(_) => return std::result::Result::Err(smoke_error("keyed market-price smoke stdin is unreadable")),
}
let secret = secret.trim().to_owned();
if secret.is_empty() {
return std::result::Result::Err(smoke_error(label));
}
return std::result::Result::Ok(secret);
}
fn keyed_setups() -> ksp_core_lib::Result<std::vec::Vec<ksp_offchain_transport_lib::MarketPriceProviderSetup>> {
if std::io::stdin().is_terminal() {
return std::result::Result::Err(smoke_error("pipe four provider API keys to the keyed market-price smoke on stdin"));
}
let birdeye_key = match read_secret_line("Birdeye API key from smoke stdin must not be empty") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let coingecko_key = match read_secret_line("CoinGecko Demo API key from smoke stdin must not be empty") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let coinmarketcap_key = match read_secret_line("CoinMarketCap Basic API key from smoke stdin must not be empty") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let jupiter_key = match read_secret_line("Jupiter Free API key from smoke stdin must not be empty") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut setups = std::vec::Vec::with_capacity(4);
let birdeye = match ksp_offchain_transport_lib::MarketPriceBirdeyeSettings::new(true, std::option::Option::Some(birdeye_key)) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::Birdeye(birdeye));
let coingecko = match ksp_offchain_transport_lib::MarketPriceCoinGeckoSettings::demo(true, std::option::Option::Some(coingecko_key)) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinGecko(coingecko));
let coinmarketcap = match ksp_offchain_transport_lib::MarketPriceCoinMarketCapSettings::basic(true, std::option::Option::Some(coinmarketcap_key)) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinMarketCap(coinmarketcap));
let jupiter = match ksp_offchain_transport_lib::MarketPriceJupiterSettings::free(true, std::option::Option::Some(jupiter_key)) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
setups.push(ksp_offchain_transport_lib::MarketPriceProviderSetup::Jupiter(jupiter));
return std::result::Result::Ok(setups);
}
fn assert_live_outcomes(
registry: &ksp_offchain_transport_lib::MarketPriceProviderRegistry,
outcomes: &[ksp_offchain_transport_lib::MarketPriceRefreshOutcome],
) {
let mut failures = std::vec::Vec::new();
for outcome in outcomes {
let provider_id = outcome.provider_id();
let observation = match outcome.observation() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
failures.push(std::format!("{}:{:?}", provider_id.as_str(), outcome.state().availability()));
continue;
},
};
assert_eq!(observation.provider_id(), provider_id);
assert_eq!(observation.pair(), ksp_offchain_transport_lib::MarketPricePair::SolUsd);
assert!(observation.price().coefficient() > 0);
assert!(observation.received_at() >= observation.request_started_at());
let descriptor = match registry.descriptor(provider_id) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
failures.push(std::format!("{}:missing_registry_descriptor", provider_id.as_str()));
continue;
},
};
assert_eq!(observation.semantics(), descriptor.semantics());
}
assert!(failures.is_empty(), "live market-price providers did not all refresh successfully after every provider was attempted: {failures:?}");
return;
}
async fn run_live_smoke(setups: std::vec::Vec<ksp_offchain_transport_lib::MarketPriceProviderSetup>) -> ksp_core_lib::Result<()> {
let service = match ksp_offchain_transport_lib::MarketPriceService::new(setups) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let registry = service.registry();
let outcomes = match service.refresh_all().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
assert_eq!(outcomes.len(), registry.len());
assert_live_outcomes(&registry, outcomes.as_slice());
return std::result::Result::Ok(());
}
#[tokio::test(flavor = "current_thread")]
#[ignore = "opt-in live SOL/USD smoke for seven keyless V1 providers; performs external HTTPS requests"]
async fn keyless_market_price_providers_live_smoke() -> ksp_core_lib::Result<()> {
let setups = match keyless_setups() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return run_live_smoke(setups).await;
}
#[tokio::test(flavor = "current_thread")]
#[ignore = "opt-in live SOL/USD smoke for four free keyed V1 modes; reads API keys from stdin in Birdeye/CoinGecko/CoinMarketCap/Jupiter order"]
async fn keyed_market_price_providers_live_smoke() -> ksp_core_lib::Result<()> {
let setups = match keyed_setups() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return run_live_smoke(setups).await;
}