Skip to content

Commit d7a3445

Browse files
granietEvolveArt
andauthored
Base routing (#33)
* first draft of routing * result of rebase * fix: normalize values before routing * fix: routing condition * fix: type --------- Co-authored-by: 0xevolve <Artevolve@yahoo.com>
1 parent e345010 commit d7a3445

File tree

7 files changed

+205
-12
lines changed

7 files changed

+205
-12
lines changed

pragma-entities/src/models/currency.rs

+16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
use super::DieselResult;
2+
use crate::schema::currencies;
3+
use diesel::{ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
14
use utoipa::ToSchema;
25
use uuid::Uuid;
36

@@ -9,3 +12,16 @@ pub struct Currency {
912
pub is_abstract: bool,
1013
pub ethereum_address: String,
1114
}
15+
16+
impl Currency {
17+
pub fn get_all(conn: &mut PgConnection) -> DieselResult<Vec<String>> {
18+
currencies::table.select(currencies::name).get_results(conn)
19+
}
20+
21+
pub fn get_abstract_all(conn: &mut PgConnection) -> DieselResult<Vec<String>> {
22+
currencies::table
23+
.select(currencies::name)
24+
.filter(currencies::abstract_.eq(true))
25+
.get_results(conn)
26+
}
27+
}

pragma-entities/src/models/entry.rs

+7
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ impl Entry {
4747
.get_results(conn)
4848
}
4949

50+
pub fn exists(conn: &mut PgConnection, pair_id: String) -> DieselResult<bool> {
51+
diesel::select(diesel::dsl::exists(
52+
entries::table.filter(entries::pair_id.eq(pair_id)),
53+
))
54+
.get_result(conn)
55+
}
56+
5057
pub fn get_by_pair_id(conn: &mut PgConnection, pair_id: String) -> DieselResult<Entry> {
5158
entries::table
5259
.filter(entries::pair_id.eq(pair_id))

pragma-node/src/handlers/entries/get_entry.rs

+12-10
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,12 @@ pub async fn get_entry(
4646
Interval::OneMinute
4747
};
4848

49+
let is_routing = params.routing.unwrap_or(false);
50+
4951
// Validate given timestamp
5052
if timestamp > now {
5153
return Err(EntryError::InvalidTimestamp);
5254
}
53-
5455
// Mock strk/eth pair
5556
if pair_id == "STRK/ETH" {
5657
return Ok(Json(GetEntryResponse {
@@ -62,14 +63,15 @@ pub async fn get_entry(
6263
}));
6364
}
6465

65-
let entry =
66-
entry_repository::get_median_price(&state.pool, pair_id.clone(), interval, timestamp)
67-
.await
68-
.map_err(|db_error| to_entry_error(db_error, &pair_id))?;
69-
70-
let decimals = entry_repository::get_decimals(&state.pool, &pair_id)
71-
.await
72-
.map_err(|db_error| to_entry_error(db_error, &pair_id))?;
66+
let (entry, decimals) = entry_repository::routing(
67+
&state.pool,
68+
pair_id.clone(),
69+
interval,
70+
timestamp,
71+
is_routing,
72+
)
73+
.await
74+
.map_err(|e| to_entry_error(e, &pair_id))?;
7375

7476
Ok(Json(adapt_entry_to_entry_response(
7577
pair_id, &entry, decimals,
@@ -97,7 +99,7 @@ fn adapt_entry_to_entry_response(
9799
}
98100
}
99101

100-
fn to_entry_error(error: InfraError, pair_id: &String) -> EntryError {
102+
pub(crate) fn to_entry_error(error: InfraError, pair_id: &String) -> EntryError {
101103
match error {
102104
InfraError::InternalServerError => EntryError::InternalServerError,
103105
InfraError::NotFound => EntryError::NotFound(pair_id.to_string()),

pragma-node/src/handlers/entries/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ pub struct GetVolatilityResponse {
6868
/// Query parameters structs
6969
7070
// Define an enum for the allowed intervals
71-
#[derive(Default, Debug, Deserialize, ToSchema)]
71+
#[derive(Default, Debug, Deserialize, ToSchema, Clone, Copy)]
7272
pub enum Interval {
7373
#[serde(rename = "1min")]
7474
#[default]
@@ -83,13 +83,15 @@ pub enum Interval {
8383
pub struct GetEntryParams {
8484
pub timestamp: Option<u64>,
8585
pub interval: Option<Interval>,
86+
pub routing: Option<bool>,
8687
}
8788

8889
impl Default for GetEntryParams {
8990
fn default() -> Self {
9091
Self {
9192
timestamp: Some(chrono::Utc::now().timestamp_millis() as u64),
9293
interval: Some(Interval::default()),
94+
routing: Some(false),
9395
}
9496
}
9597
}

pragma-node/src/infra/repositories/entry_repository.rs

+135-1
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ use pragma_entities::dto;
88
use pragma_entities::{
99
error::{adapt_infra_error, InfraError},
1010
schema::currencies,
11-
Entry, NewEntry,
11+
Currency, Entry, NewEntry,
1212
};
1313

1414
use crate::handlers::entries::Interval;
15+
use crate::utils::{convert_via_quote, normalize_to_decimals};
1516

1617
#[derive(Deserialize)]
1718
#[allow(unused)]
@@ -81,6 +82,139 @@ pub struct MedianEntryRaw {
8182
pub num_sources: i64,
8283
}
8384

85+
pub async fn routing(
86+
pool: &deadpool_diesel::postgres::Pool,
87+
pair_id: String,
88+
interval: Interval,
89+
timestamp: u64,
90+
is_routing: bool,
91+
) -> Result<(MedianEntry, u32), InfraError> {
92+
if pair_id_exist(pool, pair_id.clone()).await? || !is_routing {
93+
return get_price_decimals(pool, pair_id, interval, timestamp).await;
94+
}
95+
96+
let [base, quote]: [&str; 2] = pair_id
97+
.split('/')
98+
.collect::<Vec<_>>()
99+
.try_into()
100+
.map_err(|_| InfraError::InternalServerError)?;
101+
102+
match find_alternative_pair_price(pool, base, quote, interval, timestamp).await {
103+
Ok(result) => Ok(result),
104+
Err(_) => Err(InfraError::NotFound),
105+
}
106+
}
107+
108+
fn calculate_rebased_price(
109+
base_result: (MedianEntry, u32),
110+
quote_result: (MedianEntry, u32),
111+
) -> Result<(MedianEntry, u32), InfraError> {
112+
let (base_entry, base_decimals) = base_result;
113+
let (quote_entry, quote_decimals) = quote_result;
114+
115+
if quote_entry.median_price == BigDecimal::from(0) {
116+
return Err(InfraError::InternalServerError);
117+
}
118+
119+
let (rebase_price, decimals) = if base_decimals < quote_decimals {
120+
let normalized_base_price =
121+
normalize_to_decimals(base_entry.median_price, base_decimals, quote_decimals);
122+
(
123+
convert_via_quote(
124+
normalized_base_price,
125+
quote_entry.median_price,
126+
quote_decimals,
127+
)?,
128+
quote_decimals,
129+
)
130+
} else {
131+
let normalized_quote_price =
132+
normalize_to_decimals(quote_entry.median_price, quote_decimals, base_decimals);
133+
(
134+
convert_via_quote(
135+
base_entry.median_price,
136+
normalized_quote_price,
137+
base_decimals,
138+
)?,
139+
base_decimals,
140+
)
141+
};
142+
let min_timestamp = std::cmp::max(base_entry.time.timestamp(), quote_entry.time.timestamp());
143+
let num_sources = std::cmp::max(base_entry.num_sources, quote_entry.num_sources);
144+
let new_timestamp =
145+
NaiveDateTime::from_timestamp_opt(min_timestamp, 0).ok_or(InfraError::InvalidTimeStamp)?;
146+
147+
let median_entry = MedianEntry {
148+
time: new_timestamp,
149+
median_price: rebase_price,
150+
num_sources,
151+
};
152+
153+
Ok((median_entry, decimals))
154+
}
155+
156+
async fn find_alternative_pair_price(
157+
pool: &deadpool_diesel::postgres::Pool,
158+
base: &str,
159+
quote: &str,
160+
interval: Interval,
161+
timestamp: u64,
162+
) -> Result<(MedianEntry, u32), InfraError> {
163+
let conn = pool.get().await.map_err(adapt_infra_error)?;
164+
165+
let alternative_currencies = conn
166+
.interact(Currency::get_abstract_all)
167+
.await
168+
.map_err(adapt_infra_error)?
169+
.map_err(adapt_infra_error)?;
170+
171+
for alt_currency in alternative_currencies {
172+
let base_alt_pair = format!("{}/{}", base, alt_currency);
173+
let alt_quote_pair = format!("{}/{}", alt_currency, quote);
174+
175+
if pair_id_exist(pool, base_alt_pair.clone()).await?
176+
&& pair_id_exist(pool, alt_quote_pair.clone()).await?
177+
{
178+
let base_alt_result =
179+
get_price_decimals(pool, base_alt_pair, interval, timestamp).await?;
180+
let alt_quote_result =
181+
get_price_decimals(pool, alt_quote_pair, interval, timestamp).await?;
182+
183+
return calculate_rebased_price(base_alt_result, alt_quote_result);
184+
}
185+
}
186+
187+
Err(InfraError::NotFound)
188+
}
189+
190+
async fn pair_id_exist(
191+
pool: &deadpool_diesel::postgres::Pool,
192+
pair_id: String,
193+
) -> Result<bool, InfraError> {
194+
let conn = pool.get().await.map_err(adapt_infra_error)?;
195+
196+
let res = conn
197+
.interact(move |conn| Entry::exists(conn, pair_id))
198+
.await
199+
.map_err(adapt_infra_error)?
200+
.map_err(adapt_infra_error)?;
201+
202+
Ok(res)
203+
}
204+
205+
async fn get_price_decimals(
206+
pool: &deadpool_diesel::postgres::Pool,
207+
pair_id: String,
208+
interval: Interval,
209+
timestamp: u64,
210+
) -> Result<(MedianEntry, u32), InfraError> {
211+
let entry = get_median_price(pool, pair_id.clone(), interval, timestamp).await?;
212+
213+
let decimals = get_decimals(pool, &pair_id).await?;
214+
215+
Ok((entry, decimals))
216+
}
217+
84218
pub async fn get_median_price(
85219
pool: &deadpool_diesel::postgres::Pool,
86220
pair_id: String,

pragma-node/src/utils/conversion.rs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use bigdecimal::BigDecimal;
2+
use pragma_entities::InfraError;
3+
4+
pub fn convert_via_quote(
5+
a_price: BigDecimal,
6+
b_price: BigDecimal,
7+
output_decimals: u32,
8+
) -> Result<BigDecimal, InfraError> {
9+
if b_price == BigDecimal::from(0) {
10+
return Err(InfraError::InternalServerError);
11+
}
12+
13+
let power = BigDecimal::from(10_i64.pow(output_decimals));
14+
15+
Ok(a_price * power / b_price)
16+
}
17+
18+
pub fn normalize_to_decimals(
19+
value: BigDecimal,
20+
original_decimals: u32,
21+
target_decimals: u32,
22+
) -> BigDecimal {
23+
if target_decimals >= original_decimals {
24+
let power = BigDecimal::from(10_i64.pow(target_decimals - original_decimals));
25+
value * power
26+
} else {
27+
let power = BigDecimal::from(10_i64.pow(original_decimals - target_decimals));
28+
value / power
29+
}
30+
}

pragma-node/src/utils/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
pub use conversion::{convert_via_quote, normalize_to_decimals};
12
pub use custom_extractors::json_extractor::JsonExtractor;
23
pub use custom_extractors::path_extractor::PathExtractor;
34
pub use signing::typed_data::TypedData;
45

6+
mod conversion;
57
mod custom_extractors;
68
mod signing;

0 commit comments

Comments
 (0)