tycho_simulation/rfq/protocols/hashflow/
all_pairs_state.rs1use std::{any::Any, collections::HashMap, fmt, sync::Arc};
2
3use async_trait::async_trait;
4use num_bigint::BigUint;
5use serde::{Deserialize, Serialize};
6use tycho_common::{
7 dto::ProtocolStateDelta,
8 models::{protocol::GetAmountOutParams, token::Token},
9 simulation::{
10 errors::{SimulationError, TransitionError},
11 indicatively_priced::{IndicativelyPriced, SignedQuote},
12 protocol_sim::{Balances, GetAmountOutResult, ProtocolSim},
13 },
14 Bytes,
15};
16
17use crate::rfq::protocols::{
18 hashflow::client::HashflowClient,
19 maker_price_levels::{AllMakerPriceLevels, MakerPriceLevels, MakerPricing},
20};
21
22#[derive(Clone, Serialize, Deserialize)]
28pub struct HashflowAllPairsState {
29 pub price_levels: AllMakerPriceLevels,
30 pub client: Arc<HashflowClient>,
31}
32
33impl fmt::Debug for HashflowAllPairsState {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 f.debug_struct("HashflowAllPairsState")
36 .field("price_levels", &self.price_levels)
37 .finish_non_exhaustive()
38 }
39}
40
41impl HashflowAllPairsState {
42 pub fn new(
43 price_levels: Vec<MakerPriceLevels>,
44 tokens: HashMap<Bytes, Token>,
45 client: HashflowClient,
46 ) -> Result<Self, SimulationError> {
47 let rule = client.quote_rule();
48 let price_levels =
49 AllMakerPriceLevels::new(price_levels, tokens, MakerPricing::Hashflow, rule)?;
50 Ok(Self { price_levels, client: Arc::new(client) })
51 }
52}
53
54#[typetag::serde]
55impl ProtocolSim for HashflowAllPairsState {
56 fn fee(&self) -> f64 {
57 0.0
58 }
59
60 fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError> {
61 self.price_levels
62 .spot_price(&base.address, "e.address)
63 }
64
65 fn get_amount_out(
66 &self,
67 amount_in: BigUint,
68 token_in: &Token,
69 token_out: &Token,
70 ) -> Result<GetAmountOutResult, SimulationError> {
71 let fill =
72 self.price_levels
73 .best_fill(&amount_in, &token_in.address, &token_out.address)?;
74 let new_state = Self {
75 price_levels: self
76 .price_levels
77 .with_used(&fill.maker_levels.market_maker),
78 client: self.client.clone(),
79 };
80 fill.result(151_000, Box::new(new_state))
81 }
82
83 fn get_limits(
84 &self,
85 sell_token: Bytes,
86 buy_token: Bytes,
87 ) -> Result<(BigUint, BigUint), SimulationError> {
88 self.price_levels
89 .get_limits(&sell_token, &buy_token)
90 }
91
92 fn as_indicatively_priced(&self) -> Result<&dyn IndicativelyPriced, SimulationError> {
93 Ok(self)
94 }
95
96 fn delta_transition(
97 &mut self,
98 _delta: ProtocolStateDelta,
99 _tokens: &HashMap<Bytes, Token>,
100 _balances: &Balances,
101 ) -> Result<(), TransitionError> {
102 Err(TransitionError::DecodeError("Not implemented".into()))
103 }
104
105 fn clone_box(&self) -> Box<dyn ProtocolSim> {
106 Box::new(self.clone())
107 }
108
109 fn as_any(&self) -> &dyn Any {
110 self
111 }
112
113 fn as_any_mut(&mut self) -> &mut dyn Any {
114 self
115 }
116
117 fn eq(&self, other: &dyn ProtocolSim) -> bool {
118 let Some(other) = other
119 .as_any()
120 .downcast_ref::<HashflowAllPairsState>()
121 else {
122 return false;
123 };
124 self.price_levels == other.price_levels
125 }
126}
127
128#[async_trait]
129impl IndicativelyPriced for HashflowAllPairsState {
130 async fn request_signed_quote(
136 &self,
137 params: GetAmountOutParams,
138 ) -> Result<SignedQuote, SimulationError> {
139 let fill =
140 self.price_levels
141 .best_fill(¶ms.amount_in, ¶ms.token_in, ¶ms.token_out)?;
142 Ok(self
143 .client
144 .request_quote(¶ms, Some(&fill.maker_levels.market_maker))
145 .await?)
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use std::collections::HashSet;
152
153 use tokio::time::Duration;
154 use tycho_common::models::Chain;
155
156 use super::*;
157 use crate::rfq::{
158 models::{ComponentLayout, QuoteRule},
159 protocols::test_utils::{
160 maker_price_levels, mock_quote_server, quote_params, usdc, wbtc, weth, weth_amount,
161 HASHFLOW_QUOTE_RESPONSE,
162 },
163 };
164
165 fn client(quote_rule: QuoteRule, quote_endpoint: String) -> HashflowClient {
166 HashflowClient::new(
167 Chain::Ethereum,
168 HashSet::new(),
169 0.0,
170 HashSet::new(),
171 "".to_string(),
172 "".to_string(),
173 Duration::from_secs(0),
174 Duration::from_secs(1),
175 )
176 .unwrap()
177 .with_component_layout(ComponentLayout::AllPairs)
178 .with_quote_rule(quote_rule)
179 .with_quote_endpoint(quote_endpoint)
180 }
181
182 fn test_state(quote_rule: QuoteRule, quote_endpoint: String) -> HashflowAllPairsState {
184 HashflowAllPairsState::new(
185 vec![
186 maker_price_levels("test_mm", &weth(), &usdc(), &[(0.5, 3000.0), (1.5, 3000.0)]),
187 maker_price_levels("test_mm_2", &weth(), &usdc(), &[(0.5, 3010.0)]),
188 maker_price_levels("test_mm", &wbtc(), &usdc(), &[(1.0, 65000.0)]),
189 ],
190 HashMap::from([
191 (weth().address, weth()),
192 (usdc().address, usdc()),
193 (wbtc().address, wbtc()),
194 ]),
195 client(quote_rule, quote_endpoint),
196 )
197 .unwrap()
198 }
199
200 #[test]
201 fn get_amount_out_marks_the_maker_used() {
202 let state = test_state(QuoteRule::OncePerMaker, String::new());
203 let result = state
204 .get_amount_out(weth_amount(0.5), &weth(), &usdc())
205 .unwrap();
206 assert_eq!(result.amount, BigUint::from(1_505_000_000u64));
207 assert_eq!(result.gas, BigUint::from(151_000u64));
208 let new_state = result
209 .new_state
210 .as_any()
211 .downcast_ref::<HashflowAllPairsState>()
212 .unwrap();
213 assert_eq!(
214 new_state.price_levels,
215 state
216 .price_levels
217 .with_used("test_mm_2")
218 );
219 }
220
221 #[tokio::test]
222 async fn request_signed_quote_names_the_picked_maker() {
223 let (addr, request_log) = mock_quote_server(0, HASHFLOW_QUOTE_RESPONSE).await;
224 let state = HashflowAllPairsState::new(
226 vec![
227 maker_price_levels("test_mm", &weth(), &wbtc(), &[(1.0, 0.05)]),
228 maker_price_levels("test_mm_2", &weth(), &wbtc(), &[(1.0, 0.051)]),
229 ],
230 HashMap::from([(weth().address, weth()), (wbtc().address, wbtc())]),
231 client(QuoteRule::OncePerMaker, format!("http://127.0.0.1:{}/rfq", addr.port())),
232 )
233 .unwrap();
234 state
235 .request_signed_quote(quote_params())
236 .await
237 .unwrap();
238 let after_first = HashflowAllPairsState {
239 price_levels: state
240 .price_levels
241 .with_used("test_mm_2"),
242 client: state.client.clone(),
243 };
244 after_first
245 .request_signed_quote(quote_params())
246 .await
247 .unwrap();
248
249 let requests = request_log.lock().unwrap();
250 assert!(requests[0].contains("\"marketMakers\":[\"test_mm_2\"]"), "{}", requests[0]);
251 assert!(requests[1].contains("\"marketMakers\":[\"test_mm\"]"), "{}", requests[1]);
252 }
253}