1use std::collections::{BTreeSet, HashMap, HashSet};
2
3use futures::{stream::select_all, StreamExt};
4use tycho_client::feed::{synchronizer::ComponentWithState, FeedMessage};
5use tycho_common::{
6 models::{token::Token, Chain},
7 simulation::{errors::SimulationError, protocol_sim::ProtocolSim},
8 Bytes,
9};
10
11use crate::{
12 evm::decoder::TychoStreamDecoder,
13 protocol::{
14 errors::InvalidSnapshotError,
15 models::{ProtocolComponent, TryFromWithBlock, Update},
16 },
17 rfq::{
18 client::RFQClient,
19 models::{ComponentLayout, TimestampHeader},
20 protocols::component::{
21 decode_swap_directions, encode_swap_directions, SWAP_DIRECTIONS_ATTRIBUTE,
22 },
23 },
24};
25
26pub struct RFQStreamBuilder {
43 clients: Vec<Box<dyn RFQClient>>,
44 decoder: TychoStreamDecoder<TimestampHeader>,
45}
46
47impl Default for RFQStreamBuilder {
48 fn default() -> Self {
49 Self::new()
50 }
51}
52
53impl RFQStreamBuilder {
54 pub fn new() -> Self {
55 Self {
56 clients: Vec::new(),
57 decoder: TychoStreamDecoder::new(Chain::Ethereum),
61 }
62 }
63
64 pub fn add_client<T>(mut self, name: &str, provider: Box<dyn RFQClient>) -> Self
67 where
68 T: ProtocolSim
69 + TryFromWithBlock<ComponentWithState, TimestampHeader, Error = InvalidSnapshotError>
70 + Send
71 + 'static,
72 {
73 match provider.component_layout() {
74 ComponentLayout::PerPair => self.decoder.register_decoder::<T>(name),
75 ComponentLayout::AllPairs => self
76 .decoder
77 .register_decoder_dropping_unknown_tokens::<T>(name),
78 }
79 self.clients.push(provider);
80 self
81 }
82
83 pub async fn build(self, tx: tokio::sync::mpsc::Sender<Update>) -> Result<(), SimulationError> {
84 let streams: Vec<_> = self
85 .clients
86 .into_iter()
87 .map(|provider| provider.stream())
88 .collect();
89
90 let mut merged = select_all(streams);
91
92 while let Some(next) = merged.next().await {
93 match next {
94 Ok((provider, msg)) => {
95 let mut update = self
96 .decoder
97 .decode(&FeedMessage {
98 state_msgs: HashMap::from([(provider.clone(), msg)]),
99 sync_states: HashMap::new(),
100 })
101 .await
102 .map_err(|e| {
103 SimulationError::RecoverableError(format!("Decoding error: {e}"))
104 })?;
105 for component in update.new_pairs.values_mut() {
106 retain_carried_directions(component).map_err(|e| {
107 SimulationError::RecoverableError(format!("Decoding error: {e}"))
108 })?;
109 }
110 tx.send(update).await.map_err(|e| {
111 SimulationError::RecoverableError(format!(
112 "Failed to send update through channel: {e}"
113 ))
114 })?;
115 }
116 Err(e) => {
117 tracing::error!(
118 "RFQ stream fatal error: {e}. Assuming this stream will not emit more messages."
119 );
120 }
121 }
122 }
123
124 Ok(())
125 }
126
127 pub async fn set_tokens(self, tokens: HashMap<Bytes, Token>) -> Self {
132 self.decoder.set_tokens(tokens).await;
133 self
134 }
135}
136
137fn retain_carried_directions(component: &mut ProtocolComponent) -> Result<(), String> {
140 let Some(attribute) = component
141 .static_attributes
142 .get(SWAP_DIRECTIONS_ATTRIBUTE)
143 else {
144 return Ok(());
145 };
146 let carried: HashSet<&Bytes> = component
147 .tokens
148 .iter()
149 .map(|token| &token.address)
150 .collect();
151 let mut directions = BTreeSet::new();
152 for (token_in, token_out) in decode_swap_directions(attribute)? {
153 if carried.contains(&token_in) && carried.contains(&token_out) {
154 directions.insert((token_in, token_out));
155 }
156 }
157 let attribute = encode_swap_directions(&directions);
158 component
159 .static_attributes
160 .insert(SWAP_DIRECTIONS_ATTRIBUTE.to_string(), attribute);
161 Ok(())
162}
163
164#[cfg(test)]
165mod tests {
166 use std::{any::Any, time::Duration};
167
168 use async_trait::async_trait;
169 use futures::stream::BoxStream;
170 use num_bigint::BigUint;
171 use serde::{Deserialize, Serialize};
172 use tokio::sync::mpsc;
173 use tokio_stream::wrappers::IntervalStream;
174 use tycho_client::feed::synchronizer::{Snapshot, StateSyncMessage};
175 use tycho_common::{
176 dto::ProtocolStateDelta,
177 models::{
178 protocol::{GetAmountOutParams, ProtocolComponent, ProtocolComponentState},
179 token::Token,
180 },
181 simulation::{
182 errors::{SimulationError, TransitionError},
183 indicatively_priced::SignedQuote,
184 protocol_sim::{Balances, GetAmountOutResult},
185 },
186 Bytes,
187 };
188
189 use super::*;
190 use crate::{
191 protocol::models::DecoderContext,
192 rfq::{
193 errors::RFQError,
194 models::QuoteRule,
195 protocols::{
196 component::poll_message,
197 hashflow::all_pairs_state::HashflowAllPairsState,
198 maker_price_levels,
199 test_utils::{maker_price_levels as levels, usdc, wbtc, weth},
200 },
201 },
202 };
203
204 #[derive(Clone, Debug, Serialize, Deserialize)]
205 pub struct DummyProtocol;
206
207 #[typetag::serde]
208 impl ProtocolSim for DummyProtocol {
209 fn fee(&self) -> f64 {
210 unimplemented!("Not needed for this test")
211 }
212
213 fn spot_price(&self, _base: &Token, _quote: &Token) -> Result<f64, SimulationError> {
214 unimplemented!("Not needed for this test")
215 }
216
217 fn get_amount_out(
218 &self,
219 _amount_in: BigUint,
220 _token_in: &Token,
221 _token_out: &Token,
222 ) -> Result<GetAmountOutResult, SimulationError> {
223 unimplemented!("Not needed for this test")
224 }
225
226 fn get_limits(
227 &self,
228 _sell_token: Bytes,
229 _buy_token: Bytes,
230 ) -> Result<(BigUint, BigUint), SimulationError> {
231 unimplemented!("Not needed for this test")
232 }
233
234 fn delta_transition(
235 &mut self,
236 _delta: ProtocolStateDelta,
237 _tokens: &HashMap<Bytes, Token>,
238 _balances: &Balances,
239 ) -> Result<(), TransitionError> {
240 unimplemented!("Not needed for this test")
241 }
242
243 fn clone_box(&self) -> Box<dyn ProtocolSim> {
244 Box::new(self.clone())
245 }
246
247 fn as_any(&self) -> &dyn Any {
248 self
249 }
250
251 fn as_any_mut(&mut self) -> &mut dyn Any {
252 self
253 }
254 fn eq(&self, _other: &dyn ProtocolSim) -> bool {
255 unimplemented!("Not needed for this test")
256 }
257 }
258
259 impl TryFromWithBlock<ComponentWithState, TimestampHeader> for DummyProtocol {
260 type Error = InvalidSnapshotError;
261 async fn try_from_with_header(
262 _value: ComponentWithState,
263 _header: TimestampHeader,
264 _account_balances: &HashMap<Bytes, HashMap<Bytes, Bytes>>,
265 _all_tokens: &HashMap<Bytes, Token>,
266 _decoder_context: &DecoderContext,
267 ) -> Result<Self, Self::Error> {
268 Ok(DummyProtocol)
269 }
270 }
271
272 pub struct MockRFQClient {
273 name: String,
274 interval: Duration,
275 error_at_time: Option<u128>,
276 }
277
278 impl MockRFQClient {
279 pub fn new(name: &str, interval: Duration, error_at_time: Option<u128>) -> Self {
280 Self { name: name.to_string(), interval, error_at_time }
281 }
282 }
283
284 #[async_trait]
285 impl RFQClient for MockRFQClient {
286 fn stream(
287 &self,
288 ) -> BoxStream<'static, Result<(String, StateSyncMessage<TimestampHeader>), RFQError>>
289 {
290 let name = self.name.clone();
291 let error_at_time = self.error_at_time;
292 let mut current_time: u128 = 0;
293 let interval = self.interval;
294 let interval =
295 IntervalStream::new(tokio::time::interval(self.interval)).map(move |_| {
296 if let Some(error_at_time) = error_at_time {
297 if error_at_time == current_time {
298 return Err(RFQError::FatalError(format!(
299 "{name} stream is dying and can't go on"
300 )));
301 };
302 };
303 let protocol_component =
304 ProtocolComponent { protocol_system: name.clone(), ..Default::default() };
305
306 let snapshot = Snapshot {
307 states: HashMap::from([(
308 name.clone(),
309 ComponentWithState {
310 state: ProtocolComponentState {
311 component_id: name.clone(),
312 attributes: HashMap::new(),
313 balances: HashMap::new(),
314 },
315 component: protocol_component,
316 component_tvl: None,
317 entrypoints: vec![],
318 },
319 )]),
320 vm_storage: HashMap::new(),
321 };
322
323 let msg = StateSyncMessage {
324 header: TimestampHeader { timestamp: current_time as u64 },
325 snapshots: snapshot,
326 ..Default::default()
327 };
328
329 current_time += interval.as_millis();
330 Ok((name.clone(), msg))
331 });
332 Box::pin(interval)
333 }
334
335 async fn request_binding_quote(
336 &self,
337 _params: &GetAmountOutParams,
338 ) -> Result<SignedQuote, RFQError> {
339 unimplemented!("Not needed for this test")
340 }
341 }
342
343 #[tokio::test]
344 async fn test_rfq_stream_builder() {
345 let (tx, mut rx) = mpsc::channel::<Update>(10);
349
350 let builder = RFQStreamBuilder::new()
351 .add_client::<DummyProtocol>(
352 "bebop",
353 Box::new(MockRFQClient::new("bebop", Duration::from_millis(100), Some(300))),
354 )
355 .add_client::<DummyProtocol>(
356 "hashflow",
357 Box::new(MockRFQClient::new("hashflow", Duration::from_millis(200), None)),
358 );
359
360 tokio::spawn(builder.build(tx));
361
362 let mut updates = Vec::new();
364 for _ in 0..6 {
365 let update = rx.recv().await.unwrap();
366 updates.push(update);
367 }
368
369 let bebop_updates: Vec<_> = updates
371 .iter()
372 .filter(|u| u.new_pairs.contains_key("bebop"))
373 .collect();
374 let hashflow_updates: Vec<_> = updates
375 .iter()
376 .filter(|u| u.new_pairs.contains_key("hashflow"))
377 .collect();
378
379 assert_eq!(bebop_updates[0].block_number_or_timestamp, 0,);
380 assert_eq!(hashflow_updates[0].block_number_or_timestamp, 0,);
381 assert_eq!(bebop_updates[1].block_number_or_timestamp, 100);
382 assert_eq!(bebop_updates[2].block_number_or_timestamp, 200);
383 assert_eq!(hashflow_updates[1].block_number_or_timestamp, 200);
384 assert_eq!(bebop_updates.len(), 3);
387 assert_eq!(hashflow_updates[2].block_number_or_timestamp, 400);
388 }
389
390 struct AllPairsRFQClient(StateSyncMessage<TimestampHeader>);
392
393 #[async_trait]
394 impl RFQClient for AllPairsRFQClient {
395 fn component_layout(&self) -> ComponentLayout {
396 ComponentLayout::AllPairs
397 }
398
399 fn stream(
400 &self,
401 ) -> BoxStream<'static, Result<(String, StateSyncMessage<TimestampHeader>), RFQError>>
402 {
403 Box::pin(futures::stream::iter([Ok(("hashflow".to_string(), self.0.clone()))]))
404 }
405
406 async fn request_binding_quote(
407 &self,
408 _params: &GetAmountOutParams,
409 ) -> Result<SignedQuote, RFQError> {
410 unimplemented!("Not needed for this test")
411 }
412 }
413
414 #[tokio::test]
415 async fn test_all_pairs_component_drops_an_unknown_token_and_its_directions() {
416 std::env::set_var("HASHFLOW_USER", "test_user");
417 std::env::set_var("HASHFLOW_KEY", "test_key");
418 let component = maker_price_levels::all_pairs_component(
419 "rfq:hashflow",
420 "hashflow_pool",
421 Chain::Ethereum,
422 vec![
423 (levels("mm", &weth(), &usdc(), &[(1.0, 3000.0)]), 100.0),
424 (levels("mm", &wbtc(), &usdc(), &[(1.0, 65000.0)]), 100.0),
425 ],
426 QuoteRule::OncePerMaker,
427 )
428 .unwrap()
429 .unwrap();
430 let id = component.component.id.clone();
431 let message =
432 poll_message(&mut HashMap::new(), HashMap::from([(id.clone(), component)]), 1);
433 let (tx, mut rx) = mpsc::channel::<Update>(10);
434 let builder = RFQStreamBuilder::new()
435 .add_client::<HashflowAllPairsState>("hashflow", Box::new(AllPairsRFQClient(message)))
436 .set_tokens(HashMap::from([(weth().address, weth()), (usdc().address, usdc())]))
437 .await;
438
439 builder.build(tx).await.unwrap();
440
441 let update = rx.recv().await.unwrap();
442 let emitted = &update.new_pairs[&id];
443 assert_eq!(emitted.tokens.len(), 2);
444 assert_eq!(
445 emitted.static_attributes[SWAP_DIRECTIONS_ATTRIBUTE],
446 encode_swap_directions(&BTreeSet::from([(weth().address, usdc().address)]))
447 );
448 assert!(update.states.contains_key(&id));
449 }
450}