tycho_simulation/rfq/protocols/metric/
models.rs1use 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
135pub 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}