Skip to main content

tycho_simulation/rfq/protocols/metric/
models.rs

1use std::str::FromStr;
2
3use alloy::primitives::Address;
4use num_bigint::BigUint;
5use num_traits::ToPrimitive;
6use serde::{Deserialize, Serialize};
7use tycho_common::{models::Chain, Bytes};
8
9use crate::rfq::errors::RFQError;
10
11const Q64_FLOAT: f64 = 18_446_744_073_709_551_616.0;
12
13pub const ORACLE_UPDATE_POLICY_ATTR: &str = "oracle_update_policy";
14
15#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
16#[repr(u8)]
17pub enum MetricOracleUpdatePolicy {
18    #[default]
19    Never = 0,
20    Always = 1,
21    RetryOnRevert = 2,
22}
23
24impl MetricOracleUpdatePolicy {
25    pub fn default_for_chain(chain: Chain) -> Self {
26        match chain {
27            Chain::Ethereum => Self::RetryOnRevert,
28            _ => Self::Never,
29        }
30    }
31
32    pub fn as_attribute_value(self) -> Bytes {
33        vec![self as u8].into()
34    }
35}
36
37#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38pub struct MetricMetadata {
39    #[serde(rename = "poolAddress", deserialize_with = "deserialize_address")]
40    pub pool_address: Bytes,
41    #[serde(deserialize_with = "deserialize_address")]
42    pub token0: Bytes,
43    #[serde(deserialize_with = "deserialize_address")]
44    pub token1: Bytes,
45}
46
47fn deserialize_address<'de, D>(deserializer: D) -> Result<Bytes, D::Error>
48where
49    D: serde::Deserializer<'de>,
50{
51    let s = String::deserialize(deserializer)?;
52    let address = Address::from_str(&s).map_err(serde::de::Error::custom)?;
53    Bytes::from_str(&address.to_checksum(None)).map_err(serde::de::Error::custom)
54}
55
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57pub struct MetricBidAskResponse {
58    #[serde(rename = "bidAdj")]
59    pub bid_adj: String,
60    #[serde(rename = "askAdj")]
61    pub ask_adj: String,
62    #[serde(rename = "quoteAvailable")]
63    pub quote_available: bool,
64    #[serde(rename = "totalToken0Available")]
65    pub total_token0_available: String,
66    #[serde(rename = "totalToken1Available")]
67    pub total_token1_available: String,
68    #[serde(rename = "latestBlock")]
69    pub latest_block: u64,
70    #[serde(default)]
71    pub depth: MetricDepth,
72}
73
74#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
75pub struct MetricDepth {
76    #[serde(default)]
77    pub asks: Vec<MetricDepthBin>,
78    #[serde(default)]
79    pub bids: Vec<MetricDepthBin>,
80}
81
82#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
83pub struct MetricDepthBin {
84    #[serde(rename = "binIdx")]
85    pub bin_idx: i64,
86    pub price: String,
87    #[serde(rename = "cumulativeVolume")]
88    pub cumulative_volume: String,
89}
90
91#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
92pub struct MetricSignedOracleUpdateResponse {
93    #[serde(rename = "feedCreator", deserialize_with = "deserialize_address")]
94    pub feed_creator: Bytes,
95    #[serde(default)]
96    pub slots: Vec<MetricSignedOracleUpdateSlot>,
97}
98
99#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
100pub struct MetricSignedOracleUpdateSlot {
101    pub deadline: u64,
102    #[serde(rename = "newSlotValue")]
103    pub new_slot_value: String,
104    pub signature: Bytes,
105}
106
107impl MetricBidAskResponse {
108    pub fn bid_price(&self) -> Result<f64, RFQError> {
109        q64_decimal_to_f64(&self.bid_adj)
110    }
111
112    pub fn ask_price(&self) -> Result<f64, RFQError> {
113        q64_decimal_to_f64(&self.ask_adj)
114    }
115
116    pub fn total_token0_available(&self) -> Result<BigUint, RFQError> {
117        parse_biguint(&self.total_token0_available, "totalToken0Available")
118    }
119
120    pub fn total_token1_available(&self) -> Result<BigUint, RFQError> {
121        parse_biguint(&self.total_token1_available, "totalToken1Available")
122    }
123}
124
125impl MetricDepthBin {
126    pub fn price(&self) -> Result<f64, RFQError> {
127        q64_decimal_to_f64(&self.price)
128    }
129
130    pub fn cumulative_volume(&self) -> Result<BigUint, RFQError> {
131        parse_biguint(&self.cumulative_volume, "depth.cumulativeVolume")
132    }
133}
134
135// Metric's APIs return Q64 values as decimal strings. Convert only when pricing.
136pub fn q64_decimal_to_f64(value: &str) -> Result<f64, RFQError> {
137    let raw = parse_biguint(value, "Q64 price")?;
138    let raw = raw
139        .to_f64()
140        .ok_or_else(|| RFQError::ParsingError(format!("Q64 price does not fit in f64: {value}")))?;
141    Ok(raw / Q64_FLOAT)
142}
143
144fn parse_biguint(value: &str, field: &str) -> Result<BigUint, RFQError> {
145    BigUint::from_str(value)
146        .map_err(|_| RFQError::ParsingError(format!("Failed to parse {field}: {value}")))
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn test_q64_decimal_to_f64() {
155        let one = "18446744073709551616";
156        assert_eq!(q64_decimal_to_f64(one).unwrap(), 1.0);
157    }
158
159    #[test]
160    fn test_bid_ask_deserializes_depth_bins() {
161        let response: MetricBidAskResponse = serde_json::from_value(serde_json::json!({
162            "bidAdj": "55340232221128654848000",
163            "askAdj": "55524699661865750400000",
164            "quoteAvailable": true,
165            "totalToken0Available": "1000000000000000000",
166            "totalToken1Available": "3000000000",
167            "latestBlock": 0,
168            "depth": {
169                "asks": [{
170                    "binIdx": 0,
171                    "price": "57184906628499610009600",
172                    "cumulativeVolume": "1000000000000000000",
173                    "priceImpactE6": "33333"
174                }],
175                "bids": [{
176                    "binIdx": -1,
177                    "price": "53495557813757699686400",
178                    "cumulativeVolume": "3000000000",
179                    "priceImpactE6": "33333"
180                }]
181            }
182        }))
183        .unwrap();
184
185        assert_eq!(response.depth.asks.len(), 1);
186        assert_eq!(response.depth.bids[0].bin_idx, -1);
187        assert_eq!(response.depth.asks[0].price().unwrap(), 3100.0);
188        assert_eq!(
189            response.depth.bids[0]
190                .cumulative_volume()
191                .unwrap(),
192            BigUint::from(3_000_000_000u64)
193        );
194    }
195}