From 1c3cb365062565af3199bcbf50acb510747ebb72 Mon Sep 17 00:00:00 2001 From: Manuel Haug Date: Sat, 23 Nov 2024 13:21:23 +0100 Subject: [PATCH 1/2] add erg_xag oracle option --- core/src/datapoint_source.rs | 1 + core/src/datapoint_source/bitpanda.rs | 49 +++++++++++++++++++ core/src/datapoint_source/coingecko.rs | 45 +++++++++++++++++- core/src/datapoint_source/erg_xag.rs | 65 ++++++++++++++++++++++++++ core/src/datapoint_source/predef.rs | 3 ++ core/src/pool_config.rs | 1 + 6 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 core/src/datapoint_source/erg_xag.rs diff --git a/core/src/datapoint_source.rs b/core/src/datapoint_source.rs index 9802a7ca..89bc2b84 100644 --- a/core/src/datapoint_source.rs +++ b/core/src/datapoint_source.rs @@ -10,6 +10,7 @@ mod erg_btc; mod erg_usd; mod erg_xau; mod predef; +mod erg_xag; use crate::oracle_types::Rate; use crate::pool_config::PredefinedDataPointSource; diff --git a/core/src/datapoint_source/bitpanda.rs b/core/src/datapoint_source/bitpanda.rs index 379e2d07..c08c738f 100644 --- a/core/src/datapoint_source/bitpanda.rs +++ b/core/src/datapoint_source/bitpanda.rs @@ -2,6 +2,7 @@ use super::assets_exchange_rate::AssetsExchangeRate; use super::assets_exchange_rate::Btc; use super::assets_exchange_rate::Usd; use super::erg_xau::KgAu; +use super::erg_xag::KgAg; use super::DataPointSourceError; #[derive(Debug, Clone)] @@ -48,6 +49,47 @@ pub async fn get_kgau_usd() -> Result, DataPointSo Ok(rate) } +#[cfg(not(test))] +pub async fn get_kgag_usd() -> Result, DataPointSourceError> { + let url = "https://api.bitpanda.com/v1/ticker"; + let resp = reqwest::get(url).await?; + let json = json::parse(&resp.text().await?)?; + if let Some(p) = json["XAG"]["USD"].as_str() { + // USD price of 1 gram of silver + let p_float = p + .parse::() + .map_err(|_| DataPointSourceError::JsonMissingField { + field: "XAG.USD as f64".to_string(), + json: json.dump(), + })?; + let usd_per_kgag = KgAg::from_gram(p_float); + let rate = AssetsExchangeRate { + per1: KgAg {}, + get: Usd {}, + rate: usd_per_kgag, + }; + Ok(rate) + } else { + Err(DataPointSourceError::JsonMissingField { + field: "XAG.USD".to_string(), + json: json.dump(), + }) + } +} + +#[cfg(test)] +pub async fn get_kgag_usd() -> Result, DataPointSourceError> { + // USD price of 1 gram of silver + let p_float = 0.765; + let usd_per_kgag = KgAg::from_gram(p_float); + let rate = AssetsExchangeRate { + per1: KgAg {}, + get: Usd {}, + rate: usd_per_kgag, + }; + Ok(rate) +} + #[cfg(not(test))] // Get USD/BTC. Can be used as a redundant source for ERG/BTC through ERG/USD and USD/BTC pub(crate) async fn get_btc_usd() -> Result, DataPointSourceError> { @@ -97,6 +139,13 @@ mod tests { let pair: AssetsExchangeRate = tokio_test::block_on(get_kgau_usd()).unwrap(); assert!(pair.rate > 0.0); } + + #[test] + fn test_kgag_usd_price() { + let pair: AssetsExchangeRate = tokio_test::block_on(get_kgag_usd()).unwrap(); + assert!(pair.rate > 0.0); + } + #[test] fn test_btc_usd_price() { let pair: AssetsExchangeRate = tokio_test::block_on(get_btc_usd()).unwrap(); diff --git a/core/src/datapoint_source/coingecko.rs b/core/src/datapoint_source/coingecko.rs index 1022fe4e..35681386 100644 --- a/core/src/datapoint_source/coingecko.rs +++ b/core/src/datapoint_source/coingecko.rs @@ -6,6 +6,7 @@ use super::ada_usd::Lovelace; use super::assets_exchange_rate::Btc; use super::assets_exchange_rate::Usd; use super::erg_xau::KgAu; +use super::erg_xag::KgAg; #[cfg(not(test))] pub async fn get_kgau_nanoerg() -> Result, DataPointSourceError> { @@ -32,7 +33,7 @@ pub async fn get_kgau_nanoerg() -> Result, Dat #[cfg(test)] pub async fn get_kgau_nanoerg() -> Result, DataPointSourceError> { - let nanoerg_per_troy_ounce = NanoErg::from_erg(1.0 / 0.0008162); + let nanoerg_per_troy_ounce = NanoErg::from_erg(1.0 / 0.0482); let nanoerg_per_kg = KgAu::from_troy_ounce(nanoerg_per_troy_ounce); let rate = AssetsExchangeRate { per1: KgAu {}, @@ -42,6 +43,41 @@ pub async fn get_kgau_nanoerg() -> Result, Dat Ok(rate) } +#[cfg(not(test))] +pub async fn get_kgag_nanoerg() -> Result, DataPointSourceError> { + let url = "https://api.coingecko.com/api/v3/simple/price?ids=ergo&vs_currencies=XAG"; + let resp = reqwest::get(url).await?; + let price_json = json::parse(&resp.text().await?)?; + if let Some(p) = price_json["ergo"]["xag"].as_f64() { + // Convert from price Erg/XAG to nanoErgs per 1 XAG + let nanoerg_per_troy_ounce = NanoErg::from_erg(1.0 / p); + let nanoerg_per_kg = KgAg::from_troy_ounce(nanoerg_per_troy_ounce); + let rate = AssetsExchangeRate { + per1: KgAg {}, + get: NanoErg {}, + rate: nanoerg_per_kg, + }; + Ok(rate) + } else { + Err(DataPointSourceError::JsonMissingField { + field: "ergo.xag as f64".to_string(), + json: price_json.dump(), + }) + } +} + +#[cfg(test)] +pub async fn get_kgag_nanoerg() -> Result, DataPointSourceError> { + let nanoerg_per_troy_ounce = NanoErg::from_erg(1.0 / 0.0706); + let nanoerg_per_kg = KgAg::from_troy_ounce(nanoerg_per_troy_ounce); + let rate = AssetsExchangeRate { + per1: KgAg {}, + get: NanoErg {}, + rate: nanoerg_per_kg, + }; + Ok(rate) +} + #[cfg(not(test))] pub async fn get_usd_nanoerg() -> Result, DataPointSourceError> { let url = "https://api.coingecko.com/api/v3/simple/price?ids=ergo&vs_currencies=USD"; @@ -154,6 +190,13 @@ mod tests { tokio_test::block_on(get_kgau_nanoerg()).unwrap(); assert!(pair.rate > 0.0); } + + #[test] + fn test_erg_xag_price() { + let pair: AssetsExchangeRate = + tokio_test::block_on(get_kgag_nanoerg()).unwrap(); + assert!(pair.rate > 0.0); + } #[test] fn test_erg_usd_price() { diff --git a/core/src/datapoint_source/erg_xag.rs b/core/src/datapoint_source/erg_xag.rs new file mode 100644 index 00000000..8197c7e7 --- /dev/null +++ b/core/src/datapoint_source/erg_xag.rs @@ -0,0 +1,65 @@ +//! Obtains the nanoErg per 1 XAG (troy ounce of silver) rate + +use std::pin::Pin; + +use futures::Future; + +use crate::datapoint_source::assets_exchange_rate::{convert_rate, Asset, AssetsExchangeRate, NanoErg}; +use crate::datapoint_source::{bitpanda, coingecko, DataPointSourceError}; +use crate::datapoint_source::aggregator::fetch_aggregated; +use crate::datapoint_source::erg_usd::nanoerg_usd_sources; + +#[derive(Debug, Clone, Copy)] +pub struct KgAg {} + +#[derive(Debug, Clone, Copy)] +pub struct Xag {} + +impl Asset for KgAg {} + +impl Asset for Xag {} + +impl KgAg { + pub fn from_troy_ounce(oz: f64) -> f64 { + // https://en.wikipedia.org/wiki/Gold_bar + // troy ounces per kg + oz * 32.150746568627 + } + + pub fn from_gram(g: f64) -> f64 { + g * 1000.0 + } +} + +#[allow(clippy::type_complexity)] +pub fn nanoerg_kgag_sources() -> Vec< +Pin, DataPointSourceError>>>>, +> { + vec![ + Box::pin(coingecko::get_kgag_nanoerg()), + Box::pin(combined_kgag_nanoerg()), + ] +} + +pub async fn combined_kgag_nanoerg( +) -> Result, DataPointSourceError> { + let kgag_usd_rate = bitpanda::get_kgag_usd().await?; + let aggregated_usd_nanoerg_rate = fetch_aggregated(nanoerg_usd_sources()).await?; + Ok(convert_rate(aggregated_usd_nanoerg_rate, kgag_usd_rate)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_kgag_nanoerg_combined() { + let combined = tokio_test::block_on(combined_kgag_nanoerg()).unwrap(); + let coingecko = tokio_test::block_on(coingecko::get_kgag_nanoerg()).unwrap(); + let deviation_from_coingecko = (combined.rate - coingecko.rate).abs() / coingecko.rate; + assert!( + deviation_from_coingecko < 0.05, + "up to 5% deviation is allowed" + ); + } +} diff --git a/core/src/datapoint_source/predef.rs b/core/src/datapoint_source/predef.rs index 3df7992e..0578382b 100644 --- a/core/src/datapoint_source/predef.rs +++ b/core/src/datapoint_source/predef.rs @@ -26,6 +26,9 @@ async fn fetch_predef_source_aggregated( PredefinedDataPointSource::NanoErgXau => { fetch_aggregated(nanoerg_kgau_sources()).await?.rate } + PredefinedDataPointSource::NanoErgXag => { + fetch_aggregated(nanoerg_kgau_sources()).await?.rate + } PredefinedDataPointSource::NanoAdaUsd => { fetch_aggregated(usd_lovelace_sources()).await?.rate } diff --git a/core/src/pool_config.rs b/core/src/pool_config.rs index 33312579..04059168 100644 --- a/core/src/pool_config.rs +++ b/core/src/pool_config.rs @@ -56,6 +56,7 @@ pub struct PoolConfig { pub enum PredefinedDataPointSource { NanoErgUsd, NanoErgXau, + NanoErgXag, NanoAdaUsd, NanoErgBTC, } From ff6f3840f368fa30a4717f1002e9840b2ee1b853 Mon Sep 17 00:00:00 2001 From: Manuel Haug Date: Sat, 7 Dec 2024 14:42:57 +0100 Subject: [PATCH 2/2] add rsn_xag oracle option - add ergodex as datasource --- core/src/datapoint_source.rs | 4 +- core/src/datapoint_source/bitpanda.rs | 6 +-- core/src/datapoint_source/coingecko.rs | 66 ++++++++++++++++++++--- core/src/datapoint_source/erg_xag.rs | 10 ++-- core/src/datapoint_source/ergodex.rs | 41 +++++++++++++++ core/src/datapoint_source/predef.rs | 2 + core/src/datapoint_source/rsn_xag.rs | 72 ++++++++++++++++++++++++++ core/src/pool_config.rs | 1 + 8 files changed, 187 insertions(+), 15 deletions(-) create mode 100644 core/src/datapoint_source/ergodex.rs create mode 100644 core/src/datapoint_source/rsn_xag.rs diff --git a/core/src/datapoint_source.rs b/core/src/datapoint_source.rs index 89bc2b84..20c3a108 100644 --- a/core/src/datapoint_source.rs +++ b/core/src/datapoint_source.rs @@ -8,9 +8,11 @@ mod coingecko; mod custom_ext_script; mod erg_btc; mod erg_usd; +mod erg_xag; mod erg_xau; +mod ergodex; mod predef; -mod erg_xag; +mod rsn_xag; use crate::oracle_types::Rate; use crate::pool_config::PredefinedDataPointSource; diff --git a/core/src/datapoint_source/bitpanda.rs b/core/src/datapoint_source/bitpanda.rs index c08c738f..af57a2cb 100644 --- a/core/src/datapoint_source/bitpanda.rs +++ b/core/src/datapoint_source/bitpanda.rs @@ -1,8 +1,8 @@ use super::assets_exchange_rate::AssetsExchangeRate; use super::assets_exchange_rate::Btc; use super::assets_exchange_rate::Usd; -use super::erg_xau::KgAu; use super::erg_xag::KgAg; +use super::erg_xau::KgAu; use super::DataPointSourceError; #[derive(Debug, Clone)] @@ -139,13 +139,13 @@ mod tests { let pair: AssetsExchangeRate = tokio_test::block_on(get_kgau_usd()).unwrap(); assert!(pair.rate > 0.0); } - + #[test] fn test_kgag_usd_price() { let pair: AssetsExchangeRate = tokio_test::block_on(get_kgag_usd()).unwrap(); assert!(pair.rate > 0.0); } - + #[test] fn test_btc_usd_price() { let pair: AssetsExchangeRate = tokio_test::block_on(get_btc_usd()).unwrap(); diff --git a/core/src/datapoint_source/coingecko.rs b/core/src/datapoint_source/coingecko.rs index 35681386..1dc3ab17 100644 --- a/core/src/datapoint_source/coingecko.rs +++ b/core/src/datapoint_source/coingecko.rs @@ -1,12 +1,12 @@ -use crate::datapoint_source::assets_exchange_rate::AssetsExchangeRate; -use crate::datapoint_source::assets_exchange_rate::NanoErg; -use crate::datapoint_source::DataPointSourceError; - use super::ada_usd::Lovelace; use super::assets_exchange_rate::Btc; use super::assets_exchange_rate::Usd; -use super::erg_xau::KgAu; use super::erg_xag::KgAg; +use super::erg_xau::KgAu; +use crate::datapoint_source::assets_exchange_rate::AssetsExchangeRate; +use crate::datapoint_source::assets_exchange_rate::NanoErg; +use crate::datapoint_source::rsn_xag::Rsn; +use crate::datapoint_source::DataPointSourceError; #[cfg(not(test))] pub async fn get_kgau_nanoerg() -> Result, DataPointSourceError> { @@ -180,6 +180,46 @@ pub async fn get_btc_nanoerg() -> Result, DataP Ok(rate) } +pub async fn get_kgag_rsn() -> Result, DataPointSourceError> { + let url = "https://api.coingecko.com/api/v3/simple/price?ids=rosen-bridge&vs_currencies=XAG"; + let resp = reqwest::get(url).await?; + let price_json = json::parse(&resp.text().await?)?; + if let Some(p) = price_json["rosen-bridge"]["xag"].as_f64() { + // Convert from price RSN/XAG + let rsn_per_ag = KgAg::from_troy_ounce(1.0 / p); + let rate = AssetsExchangeRate { + per1: KgAg {}, + get: Rsn {}, + rate: rsn_per_ag, + }; + Ok(rate) + } else { + Err(DataPointSourceError::JsonMissingField { + field: "rsn.xag as f64".to_string(), + json: price_json.dump(), + }) + } +} + +pub async fn get_rsn_usd() -> Result, DataPointSourceError> { + let url = "https://api.coingecko.com/api/v3/simple/price?ids=rosen-bridge&vs_currencies=USD"; + let resp = reqwest::get(url).await?; + let price_json = json::parse(&resp.text().await?)?; + if let Some(p) = price_json["rosen-bridge"]["usd"].as_f64() { + let rate = AssetsExchangeRate { + per1: Usd {}, + get: Rsn {}, + rate: 1.0 / p, + }; + Ok(rate) + } else { + Err(DataPointSourceError::JsonMissingField { + field: "rsn.usd as f64".to_string(), + json: price_json.dump(), + }) + } +} + #[cfg(test)] mod tests { use super::*; @@ -190,10 +230,10 @@ mod tests { tokio_test::block_on(get_kgau_nanoerg()).unwrap(); assert!(pair.rate > 0.0); } - + #[test] fn test_erg_xag_price() { - let pair: AssetsExchangeRate = + let pair: AssetsExchangeRate = tokio_test::block_on(get_kgag_nanoerg()).unwrap(); assert!(pair.rate > 0.0); } @@ -217,4 +257,16 @@ mod tests { tokio_test::block_on(get_btc_nanoerg()).unwrap(); assert!(pair.rate > 0.0); } + + #[test] + fn test_rsn_xag_price() { + let pair: AssetsExchangeRate = tokio_test::block_on(get_kgag_rsn()).unwrap(); + assert!(pair.rate > 0.0); + } + + #[test] + fn test_rsn_usd_price() { + let pair: AssetsExchangeRate = tokio_test::block_on(get_rsn_usd()).unwrap(); + assert!(pair.rate > 0.0); + } } diff --git a/core/src/datapoint_source/erg_xag.rs b/core/src/datapoint_source/erg_xag.rs index 8197c7e7..57eccfa6 100644 --- a/core/src/datapoint_source/erg_xag.rs +++ b/core/src/datapoint_source/erg_xag.rs @@ -4,10 +4,12 @@ use std::pin::Pin; use futures::Future; -use crate::datapoint_source::assets_exchange_rate::{convert_rate, Asset, AssetsExchangeRate, NanoErg}; -use crate::datapoint_source::{bitpanda, coingecko, DataPointSourceError}; use crate::datapoint_source::aggregator::fetch_aggregated; +use crate::datapoint_source::assets_exchange_rate::{ + convert_rate, Asset, AssetsExchangeRate, NanoErg, +}; use crate::datapoint_source::erg_usd::nanoerg_usd_sources; +use crate::datapoint_source::{bitpanda, coingecko, DataPointSourceError}; #[derive(Debug, Clone, Copy)] pub struct KgAg {} @@ -25,7 +27,7 @@ impl KgAg { // troy ounces per kg oz * 32.150746568627 } - + pub fn from_gram(g: f64) -> f64 { g * 1000.0 } @@ -33,7 +35,7 @@ impl KgAg { #[allow(clippy::type_complexity)] pub fn nanoerg_kgag_sources() -> Vec< -Pin, DataPointSourceError>>>>, + Pin, DataPointSourceError>>>>, > { vec![ Box::pin(coingecko::get_kgag_nanoerg()), diff --git a/core/src/datapoint_source/ergodex.rs b/core/src/datapoint_source/ergodex.rs new file mode 100644 index 00000000..d0d2a08f --- /dev/null +++ b/core/src/datapoint_source/ergodex.rs @@ -0,0 +1,41 @@ +use crate::datapoint_source::assets_exchange_rate::{AssetsExchangeRate, NanoErg}; +use crate::datapoint_source::rsn_xag::Rsn; +use crate::datapoint_source::DataPointSourceError; + +pub async fn get_rsn_nanoerg() -> Result, DataPointSourceError> { + let url = "https://api.spectrum.fi/v1/amm/pool/1b694b15467c62f0cd4525e368dbdea2329c713aa200b73df4a622e950551b40/stats"; + let resp = reqwest::get(url).await?; + let pool_json = json::parse(&resp.text().await?)?; + let locked_erg = pool_json["lockedX"]["amount"].as_f64().ok_or_else(|| { + DataPointSourceError::JsonMissingField { + field: "lockedX.amount as f64".to_string(), + json: pool_json.dump(), + } + })?; + + let locked_rsn = pool_json["lockedY"]["amount"].as_f64().ok_or_else(|| { + DataPointSourceError::JsonMissingField { + field: "lockedY.amount as f64".to_string(), + json: pool_json.dump(), + } + })?; + let price = Rsn::from_rsn(Rsn::from_rsn(locked_rsn) / NanoErg::from_erg(locked_erg)); + let rate = AssetsExchangeRate { + per1: NanoErg {}, + get: Rsn {}, + rate: price, + }; + Ok(rate) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rsn_nanoerg_price() { + let pair: AssetsExchangeRate = + tokio_test::block_on(get_rsn_nanoerg()).unwrap(); + assert!(pair.rate > 0.0); + } +} diff --git a/core/src/datapoint_source/predef.rs b/core/src/datapoint_source/predef.rs index 0578382b..e4602cdb 100644 --- a/core/src/datapoint_source/predef.rs +++ b/core/src/datapoint_source/predef.rs @@ -1,3 +1,4 @@ +use crate::datapoint_source::rsn_xag::rsn_kgag_sources; use crate::oracle_types::Rate; use super::ada_usd::usd_lovelace_sources; @@ -35,6 +36,7 @@ async fn fetch_predef_source_aggregated( PredefinedDataPointSource::NanoErgBTC => { fetch_aggregated(nanoerg_btc_sources()).await?.rate } + PredefinedDataPointSource::RsnXag => fetch_aggregated(rsn_kgag_sources()).await?.rate, }; Ok((rate_float as i64).into()) } diff --git a/core/src/datapoint_source/rsn_xag.rs b/core/src/datapoint_source/rsn_xag.rs new file mode 100644 index 00000000..e0380800 --- /dev/null +++ b/core/src/datapoint_source/rsn_xag.rs @@ -0,0 +1,72 @@ +use futures::Future; +use std::pin::Pin; + +use crate::datapoint_source::assets_exchange_rate::{convert_rate, Asset, AssetsExchangeRate}; +use crate::datapoint_source::erg_xag::KgAg; +use crate::datapoint_source::{bitpanda, coingecko, ergodex, DataPointSourceError}; + +#[derive(Debug, Clone, Copy)] +pub struct Rsn {} + +impl Asset for Rsn {} + +impl Rsn { + pub fn from_rsn(rsn: f64) -> f64 { + rsn * 1_000.0 + } +} + +#[allow(clippy::type_complexity)] +pub fn rsn_kgag_sources( +) -> Vec, DataPointSourceError>>>>> +{ + vec![ + Box::pin(coingecko::get_kgag_rsn()), + Box::pin(get_rsn_kgag_erg()), + Box::pin(get_rsn_kgag_usd()), + ] +} + +// Calculate RSN/KGAG through RSN/USD and KGAG/USD +async fn get_rsn_kgag_usd() -> Result, DataPointSourceError> { + Ok(convert_rate( + coingecko::get_rsn_usd().await?, + bitpanda::get_kgag_usd().await?, + )) +} + +// Calculate KGAG/RSN through KGAG/ERG and ERG/RSN +async fn get_rsn_kgag_erg() -> Result, DataPointSourceError> { + Ok(convert_rate( + ergodex::get_rsn_nanoerg().await?, + coingecko::get_kgag_nanoerg().await?, + )) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_kgag_rsn_combined() { + let combined = tokio_test::block_on(get_rsn_kgag_usd()).unwrap(); + let coingecko = tokio_test::block_on(coingecko::get_kgag_rsn()).unwrap(); + let ergodex = tokio_test::block_on(get_rsn_kgag_erg()).unwrap(); + let deviation_from_coingecko = (combined.rate - coingecko.rate).abs() / coingecko.rate; + assert!( + deviation_from_coingecko < 0.05, + "up to 5% deviation is allowed" + ); + let ergodex_deviation_from_coingecko = + (ergodex.rate - coingecko.rate).abs() / coingecko.rate; + assert!( + ergodex_deviation_from_coingecko < 0.05, + "up to 5% deviation is allowed" + ); + let deviation_from_ergodex = (ergodex.rate - combined.rate).abs() / combined.rate; + assert!( + deviation_from_ergodex < 0.05, + "up to 5% deviation is allowed" + ); + } +} diff --git a/core/src/pool_config.rs b/core/src/pool_config.rs index 04059168..a3a177d0 100644 --- a/core/src/pool_config.rs +++ b/core/src/pool_config.rs @@ -59,6 +59,7 @@ pub enum PredefinedDataPointSource { NanoErgXag, NanoAdaUsd, NanoErgBTC, + RsnXag, } /// Holds the token ids of every important token used by the oracle pool.