From 5a67dc154d8d9cea2d8a8c14dff28fc2f3d83409 Mon Sep 17 00:00:00 2001 From: Kamal Ahmad Date: Fri, 14 Jul 2023 01:57:38 +0500 Subject: [PATCH 1/3] Add ERG/BTC sources --- core/src/datapoint_source.rs | 1 + .../datapoint_source/assets_exchange_rate.rs | 16 +++++++ core/src/datapoint_source/bitpanda.rs | 36 ++++++++++++++++ core/src/datapoint_source/coincap.rs | 41 ++++++++++++++++++ core/src/datapoint_source/coingecko.rs | 28 ++++++++++++ core/src/datapoint_source/erg_btc.rs | 43 +++++++++++++++++++ core/src/datapoint_source/erg_xau.rs | 8 +--- core/src/datapoint_source/predef.rs | 4 ++ core/src/pool_config.rs | 1 + 9 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 core/src/datapoint_source/erg_btc.rs diff --git a/core/src/datapoint_source.rs b/core/src/datapoint_source.rs index b3fdc089..9802a7ca 100644 --- a/core/src/datapoint_source.rs +++ b/core/src/datapoint_source.rs @@ -6,6 +6,7 @@ mod bitpanda; mod coincap; mod coingecko; mod custom_ext_script; +mod erg_btc; mod erg_usd; mod erg_xau; mod predef; diff --git a/core/src/datapoint_source/assets_exchange_rate.rs b/core/src/datapoint_source/assets_exchange_rate.rs index 1f90aee1..01f6ea10 100644 --- a/core/src/datapoint_source/assets_exchange_rate.rs +++ b/core/src/datapoint_source/assets_exchange_rate.rs @@ -9,9 +9,13 @@ pub struct Erg {} #[derive(Debug, Clone, Copy)] pub struct Usd {} +#[derive(Debug, Clone, Copy)] +pub struct Btc {} + impl Asset for Erg {} impl Asset for NanoErg {} impl Asset for Usd {} +impl Asset for Btc {} impl Erg { pub fn to_nanoerg(erg: f64) -> f64 { @@ -32,3 +36,15 @@ pub struct AssetsExchangeRate { pub get: GET, pub rate: f64, } + +// Calculates an Exchange Rate of GET/PER2 based on GET/PER1 and PER1/PER2 +pub fn convert( + a: AssetsExchangeRate, + b: AssetsExchangeRate, +) -> AssetsExchangeRate { + AssetsExchangeRate { + per1: b.per1, + get: a.get, + rate: a.rate * b.rate, + } +} diff --git a/core/src/datapoint_source/bitpanda.rs b/core/src/datapoint_source/bitpanda.rs index 60779bff..0301c6a3 100644 --- a/core/src/datapoint_source/bitpanda.rs +++ b/core/src/datapoint_source/bitpanda.rs @@ -1,4 +1,6 @@ use super::assets_exchange_rate::AssetsExchangeRate; +#[cfg(test)] +use super::assets_exchange_rate::Btc; use super::assets_exchange_rate::Usd; use super::erg_xau::KgAu; use super::DataPointSourceError; @@ -33,6 +35,35 @@ pub async fn get_kgau_usd() -> Result, DataPointSo } } +// Currently only used for testing +#[cfg(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> { + 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["BTC"]["USD"].as_str() { + // USD price of BTC + let usd_per_btc = p + .parse::() + .map_err(|_| DataPointSourceError::JsonMissingField { + field: "BTC.USD as f64".to_string(), + json: json.dump(), + })?; + let rate = AssetsExchangeRate { + per1: Btc {}, + get: Usd {}, + rate: usd_per_btc, + }; + Ok(rate) + } else { + Err(DataPointSourceError::JsonMissingField { + field: "BTC.USD".to_string(), + json: json.dump(), + }) + } +} + #[cfg(test)] mod tests { use super::*; @@ -42,4 +73,9 @@ mod tests { let pair: AssetsExchangeRate = tokio_test::block_on(get_kgau_usd()).unwrap(); assert!(pair.rate > 0.0); } + #[test] + fn test_btc_usd_price() { + let pair: AssetsExchangeRate = tokio_test::block_on(get_btc_usd()).unwrap(); + assert!(pair.rate > 0.0); + } } diff --git a/core/src/datapoint_source/coincap.rs b/core/src/datapoint_source/coincap.rs index f5d244ed..ec47a6f3 100644 --- a/core/src/datapoint_source/coincap.rs +++ b/core/src/datapoint_source/coincap.rs @@ -1,4 +1,5 @@ use super::assets_exchange_rate::AssetsExchangeRate; +use super::assets_exchange_rate::Btc; use super::assets_exchange_rate::NanoErg; use super::assets_exchange_rate::Usd; use super::DataPointSourceError; @@ -33,8 +34,36 @@ pub async fn get_usd_nanoerg() -> Result, DataP } } +// Get USD/BTC. Can be used as a redundant source for ERG/BTC through ERG/USD and USD/BTC +pub async fn get_btc_usd() -> Result, DataPointSourceError> { + // see https://coincap.io/assets/ergo + let url = "https://api.coincap.io/v2/assets/bitcoin"; + let resp = reqwest::get(url).await?; + let price_json = json::parse(&resp.text().await?)?; + if let Some(p) = price_json["data"]["priceUsd"].as_str() { + let usd_per_btc = p + .parse::() + .map_err(|_| DataPointSourceError::JsonMissingField { + field: "data.priceUsd as f64".to_string(), + json: price_json.dump(), + })?; + let rate = AssetsExchangeRate { + per1: Btc {}, + get: Usd {}, + rate: usd_per_btc, + }; + Ok(rate) + } else { + Err(DataPointSourceError::JsonMissingField { + field: "btc.priceUsd as string".to_string(), + json: price_json.dump(), + }) + } +} + #[cfg(test)] mod tests { + use super::super::bitpanda; use super::super::coingecko; use super::*; @@ -49,4 +78,16 @@ mod tests { "up to 5% deviation is allowed" ); } + #[test] + fn test_usd_btc_price() { + let pair = tokio_test::block_on(get_btc_usd()).unwrap(); + let bitpanda = tokio_test::block_on(bitpanda::get_btc_usd()).unwrap(); + assert!(pair.rate > 0.0); + dbg!(pair, bitpanda); + let deviation_from_bitpanda = (pair.rate - bitpanda.rate).abs() / bitpanda.rate; + assert!( + deviation_from_bitpanda < 0.05, + "up to 5% deviation is allowed" + ); + } } diff --git a/core/src/datapoint_source/coingecko.rs b/core/src/datapoint_source/coingecko.rs index 39f0bc0d..33dca31e 100644 --- a/core/src/datapoint_source/coingecko.rs +++ b/core/src/datapoint_source/coingecko.rs @@ -3,6 +3,7 @@ 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; @@ -70,6 +71,27 @@ pub async fn get_usd_lovelace() -> Result, Dat } } +pub async fn get_btc_nanoerg() -> Result, DataPointSourceError> { + let url = "https://api.coingecko.com/api/v3/simple/price?ids=ergo&vs_currencies=BTC"; + let resp = reqwest::get(url).await?; + let price_json = json::parse(&resp.text().await?)?; + if let Some(p) = price_json["ergo"]["btc"].as_f64() { + // Convert from price BTC/ERG to nanoERG/BTC + let erg_per_usd = NanoErg::from_erg(1.0 / p); + let rate = AssetsExchangeRate { + per1: Btc {}, + get: NanoErg {}, + rate: erg_per_usd, + }; + Ok(rate) + } else { + Err(DataPointSourceError::JsonMissingField { + field: "ergo.btc as f64".to_string(), + json: price_json.dump(), + }) + } +} + #[cfg(test)] mod tests { use super::*; @@ -94,4 +116,10 @@ mod tests { tokio_test::block_on(get_usd_lovelace()).unwrap(); assert!(pair.rate > 0.0); } + #[test] + fn test_erg_btc_price() { + let pair: AssetsExchangeRate = + tokio_test::block_on(get_btc_nanoerg()).unwrap(); + assert!(pair.rate > 0.0); + } } diff --git a/core/src/datapoint_source/erg_btc.rs b/core/src/datapoint_source/erg_btc.rs new file mode 100644 index 00000000..aa2a814a --- /dev/null +++ b/core/src/datapoint_source/erg_btc.rs @@ -0,0 +1,43 @@ +use std::pin::Pin; + +use futures::Future; + +use super::{ + assets_exchange_rate::{convert, AssetsExchangeRate, Btc, NanoErg}, + coincap, coingecko, DataPointSourceError, +}; + +#[allow(clippy::type_complexity)] +pub fn nanoerg_btc_sources() -> Vec< + Pin, DataPointSourceError>>>>, +> { + vec![ + Box::pin(coingecko::get_btc_nanoerg()), + Box::pin(get_btc_nanoerg_coincap()), + ] +} + +// Calculate ERG/BTC through ERG/USD and USD/BTC +async fn get_btc_nanoerg_coincap() -> Result, DataPointSourceError> +{ + Ok(convert( + coincap::get_usd_nanoerg().await?, + coincap::get_btc_usd().await?, + )) +} + +#[cfg(test)] +mod test { + use super::coingecko; + use super::get_btc_nanoerg_coincap; + #[test] + fn test_btc_nanoerg_combined() { + let combined = tokio_test::block_on(get_btc_nanoerg_coincap()).unwrap(); + let coingecko = tokio_test::block_on(coingecko::get_btc_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/erg_xau.rs b/core/src/datapoint_source/erg_xau.rs index e984bce3..a59b5e1c 100644 --- a/core/src/datapoint_source/erg_xau.rs +++ b/core/src/datapoint_source/erg_xau.rs @@ -5,6 +5,7 @@ use std::pin::Pin; use futures::Future; use super::aggregator::fetch_aggregated; +use super::assets_exchange_rate::convert; use super::assets_exchange_rate::Asset; use super::assets_exchange_rate::AssetsExchangeRate; use super::assets_exchange_rate::NanoErg; @@ -48,12 +49,7 @@ pub async fn combined_kgau_nanoerg( ) -> Result, DataPointSourceError> { let kgau_usd_rate = bitpanda::get_kgau_usd().await?; let aggregated_usd_nanoerg_rate = fetch_aggregated(nanoerg_usd_sources()).await?; - let rate = kgau_usd_rate.rate * aggregated_usd_nanoerg_rate.rate; - Ok(AssetsExchangeRate { - per1: KgAu {}, - get: NanoErg {}, - rate, - }) + Ok(convert(aggregated_usd_nanoerg_rate, kgau_usd_rate)) } #[cfg(test)] diff --git a/core/src/datapoint_source/predef.rs b/core/src/datapoint_source/predef.rs index 55e70722..3df7992e 100644 --- a/core/src/datapoint_source/predef.rs +++ b/core/src/datapoint_source/predef.rs @@ -2,6 +2,7 @@ use crate::oracle_types::Rate; use super::ada_usd::usd_lovelace_sources; use super::aggregator::fetch_aggregated; +use super::erg_btc::nanoerg_btc_sources; use super::erg_usd::nanoerg_usd_sources; use super::erg_xau::nanoerg_kgau_sources; use super::DataPointSourceError; @@ -28,6 +29,9 @@ async fn fetch_predef_source_aggregated( PredefinedDataPointSource::NanoAdaUsd => { fetch_aggregated(usd_lovelace_sources()).await?.rate } + PredefinedDataPointSource::NanoErgBTC => { + fetch_aggregated(nanoerg_btc_sources()).await?.rate + } }; Ok((rate_float as i64).into()) } diff --git a/core/src/pool_config.rs b/core/src/pool_config.rs index 737b809c..2d7eac9d 100644 --- a/core/src/pool_config.rs +++ b/core/src/pool_config.rs @@ -56,6 +56,7 @@ pub enum PredefinedDataPointSource { NanoErgUsd, NanoErgXau, NanoAdaUsd, + NanoErgBTC, } /// Holds the token ids of every important token used by the oracle pool. From beb9ef75ab513a9b7b710a03f631f2f08852157a Mon Sep 17 00:00:00 2001 From: Kamal Ahmad Date: Fri, 21 Jul 2023 16:56:28 +0500 Subject: [PATCH 2/3] Rename convert to convert_rate --- core/src/datapoint_source/assets_exchange_rate.rs | 2 +- core/src/datapoint_source/erg_btc.rs | 4 ++-- core/src/datapoint_source/erg_xau.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/datapoint_source/assets_exchange_rate.rs b/core/src/datapoint_source/assets_exchange_rate.rs index 01f6ea10..9f412220 100644 --- a/core/src/datapoint_source/assets_exchange_rate.rs +++ b/core/src/datapoint_source/assets_exchange_rate.rs @@ -38,7 +38,7 @@ pub struct AssetsExchangeRate { } // Calculates an Exchange Rate of GET/PER2 based on GET/PER1 and PER1/PER2 -pub fn convert( +pub fn convert_rate( a: AssetsExchangeRate, b: AssetsExchangeRate, ) -> AssetsExchangeRate { diff --git a/core/src/datapoint_source/erg_btc.rs b/core/src/datapoint_source/erg_btc.rs index aa2a814a..a84eb141 100644 --- a/core/src/datapoint_source/erg_btc.rs +++ b/core/src/datapoint_source/erg_btc.rs @@ -3,7 +3,7 @@ use std::pin::Pin; use futures::Future; use super::{ - assets_exchange_rate::{convert, AssetsExchangeRate, Btc, NanoErg}, + assets_exchange_rate::{convert_rate, AssetsExchangeRate, Btc, NanoErg}, coincap, coingecko, DataPointSourceError, }; @@ -20,7 +20,7 @@ pub fn nanoerg_btc_sources() -> Vec< // Calculate ERG/BTC through ERG/USD and USD/BTC async fn get_btc_nanoerg_coincap() -> Result, DataPointSourceError> { - Ok(convert( + Ok(convert_rate( coincap::get_usd_nanoerg().await?, coincap::get_btc_usd().await?, )) diff --git a/core/src/datapoint_source/erg_xau.rs b/core/src/datapoint_source/erg_xau.rs index a59b5e1c..b0b680f2 100644 --- a/core/src/datapoint_source/erg_xau.rs +++ b/core/src/datapoint_source/erg_xau.rs @@ -5,7 +5,7 @@ use std::pin::Pin; use futures::Future; use super::aggregator::fetch_aggregated; -use super::assets_exchange_rate::convert; +use super::assets_exchange_rate::convert_rate; use super::assets_exchange_rate::Asset; use super::assets_exchange_rate::AssetsExchangeRate; use super::assets_exchange_rate::NanoErg; @@ -49,7 +49,7 @@ pub async fn combined_kgau_nanoerg( ) -> Result, DataPointSourceError> { let kgau_usd_rate = bitpanda::get_kgau_usd().await?; let aggregated_usd_nanoerg_rate = fetch_aggregated(nanoerg_usd_sources()).await?; - Ok(convert(aggregated_usd_nanoerg_rate, kgau_usd_rate)) + Ok(convert_rate(aggregated_usd_nanoerg_rate, kgau_usd_rate)) } #[cfg(test)] From 224e7cbdfbf572154816e01e465490feb8ea642f Mon Sep 17 00:00:00 2001 From: Kamal Ahmad Date: Fri, 21 Jul 2023 17:53:44 +0500 Subject: [PATCH 3/3] Add combined ERG/BTC bitpanda+coincap source --- core/src/datapoint_source/bitpanda.rs | 3 --- core/src/datapoint_source/erg_btc.rs | 24 +++++++++++++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/core/src/datapoint_source/bitpanda.rs b/core/src/datapoint_source/bitpanda.rs index 0301c6a3..ec6c6074 100644 --- a/core/src/datapoint_source/bitpanda.rs +++ b/core/src/datapoint_source/bitpanda.rs @@ -1,5 +1,4 @@ use super::assets_exchange_rate::AssetsExchangeRate; -#[cfg(test)] use super::assets_exchange_rate::Btc; use super::assets_exchange_rate::Usd; use super::erg_xau::KgAu; @@ -35,8 +34,6 @@ pub async fn get_kgau_usd() -> Result, DataPointSo } } -// Currently only used for testing -#[cfg(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> { let url = "https://api.bitpanda.com/v1/ticker"; diff --git a/core/src/datapoint_source/erg_btc.rs b/core/src/datapoint_source/erg_btc.rs index a84eb141..a708e660 100644 --- a/core/src/datapoint_source/erg_btc.rs +++ b/core/src/datapoint_source/erg_btc.rs @@ -4,7 +4,7 @@ use futures::Future; use super::{ assets_exchange_rate::{convert_rate, AssetsExchangeRate, Btc, NanoErg}, - coincap, coingecko, DataPointSourceError, + bitpanda, coincap, coingecko, DataPointSourceError, }; #[allow(clippy::type_complexity)] @@ -14,6 +14,7 @@ pub fn nanoerg_btc_sources() -> Vec< vec![ Box::pin(coingecko::get_btc_nanoerg()), Box::pin(get_btc_nanoerg_coincap()), + Box::pin(get_btc_nanoerg_bitpanda()), ] } @@ -26,18 +27,39 @@ async fn get_btc_nanoerg_coincap() -> Result, D )) } +async fn get_btc_nanoerg_bitpanda() -> Result, DataPointSourceError> +{ + Ok(convert_rate( + coincap::get_usd_nanoerg().await?, + bitpanda::get_btc_usd().await?, + )) +} + #[cfg(test)] mod test { use super::coingecko; + use super::get_btc_nanoerg_bitpanda; use super::get_btc_nanoerg_coincap; #[test] fn test_btc_nanoerg_combined() { let combined = tokio_test::block_on(get_btc_nanoerg_coincap()).unwrap(); let coingecko = tokio_test::block_on(coingecko::get_btc_nanoerg()).unwrap(); + let bitpanda = tokio_test::block_on(get_btc_nanoerg_bitpanda()).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 bitpanda_deviation_from_coingecko = + (bitpanda.rate - coingecko.rate).abs() / coingecko.rate; + assert!( + bitpanda_deviation_from_coingecko < 0.05, + "up to 5% deviation is allowed" + ); + let deviation_from_bitpanda = (bitpanda.rate - combined.rate).abs() / combined.rate; + assert!( + deviation_from_bitpanda < 0.05, + "up to 5% deviation is allowed" + ); } }