1use std::collections::HashMap;
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::{TryFromWithBlock, Update},
16 },
17 rfq::{client::RFQClient, models::TimestampHeader},
18};
19
20pub struct RFQStreamBuilder {
37 clients: Vec<Box<dyn RFQClient>>,
38 decoder: TychoStreamDecoder<TimestampHeader>,
39}
40
41impl Default for RFQStreamBuilder {
42 fn default() -> Self {
43 Self::new()
44 }
45}
46
47impl RFQStreamBuilder {
48 pub fn new() -> Self {
49 Self {
50 clients: Vec::new(),
51 decoder: TychoStreamDecoder::new(Chain::Ethereum),
55 }
56 }
57
58 pub fn add_client<T>(mut self, name: &str, provider: Box<dyn RFQClient>) -> Self
59 where
60 T: ProtocolSim
61 + TryFromWithBlock<ComponentWithState, TimestampHeader, Error = InvalidSnapshotError>
62 + Send
63 + 'static,
64 {
65 self.clients.push(provider);
66 self.decoder.register_decoder::<T>(name);
67 self
68 }
69
70 pub async fn build(self, tx: tokio::sync::mpsc::Sender<Update>) -> Result<(), SimulationError> {
71 let streams: Vec<_> = self
72 .clients
73 .into_iter()
74 .map(|provider| provider.stream())
75 .collect();
76
77 let mut merged = select_all(streams);
78
79 while let Some(next) = merged.next().await {
80 match next {
81 Ok((provider, msg)) => {
82 let update = self
83 .decoder
84 .decode(&FeedMessage {
85 state_msgs: HashMap::from([(provider.clone(), msg)]),
86 sync_states: HashMap::new(),
87 })
88 .await
89 .map_err(|e| {
90 SimulationError::RecoverableError(format!("Decoding error: {e}"))
91 })?;
92 tx.send(update).await.map_err(|e| {
93 SimulationError::RecoverableError(format!(
94 "Failed to send update through channel: {e}"
95 ))
96 })?;
97 }
98 Err(e) => {
99 tracing::error!(
100 "RFQ stream fatal error: {e}. Assuming this stream will not emit more messages."
101 );
102 }
103 }
104 }
105
106 Ok(())
107 }
108
109 pub async fn set_tokens(self, tokens: HashMap<Bytes, Token>) -> Self {
114 self.decoder.set_tokens(tokens).await;
115 self
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use std::{any::Any, time::Duration};
122
123 use async_trait::async_trait;
124 use futures::stream::BoxStream;
125 use num_bigint::BigUint;
126 use serde::{Deserialize, Serialize};
127 use tokio::sync::mpsc;
128 use tokio_stream::wrappers::IntervalStream;
129 use tycho_client::feed::synchronizer::{Snapshot, StateSyncMessage};
130 use tycho_common::{
131 dto::ProtocolStateDelta,
132 models::{
133 protocol::{GetAmountOutParams, ProtocolComponent, ProtocolComponentState},
134 token::Token,
135 },
136 simulation::{
137 errors::{SimulationError, TransitionError},
138 indicatively_priced::SignedQuote,
139 protocol_sim::{Balances, GetAmountOutResult},
140 },
141 Bytes,
142 };
143
144 use super::*;
145 use crate::{protocol::models::DecoderContext, rfq::errors::RFQError};
146
147 #[derive(Clone, Debug, Serialize, Deserialize)]
148 pub struct DummyProtocol;
149
150 #[typetag::serde]
151 impl ProtocolSim for DummyProtocol {
152 fn fee(&self) -> f64 {
153 unimplemented!("Not needed for this test")
154 }
155
156 fn spot_price(&self, _base: &Token, _quote: &Token) -> Result<f64, SimulationError> {
157 unimplemented!("Not needed for this test")
158 }
159
160 fn get_amount_out(
161 &self,
162 _amount_in: BigUint,
163 _token_in: &Token,
164 _token_out: &Token,
165 ) -> Result<GetAmountOutResult, SimulationError> {
166 unimplemented!("Not needed for this test")
167 }
168
169 fn get_limits(
170 &self,
171 _sell_token: Bytes,
172 _buy_token: Bytes,
173 ) -> Result<(BigUint, BigUint), SimulationError> {
174 unimplemented!("Not needed for this test")
175 }
176
177 fn delta_transition(
178 &mut self,
179 _delta: ProtocolStateDelta,
180 _tokens: &HashMap<Bytes, Token>,
181 _balances: &Balances,
182 ) -> Result<(), TransitionError> {
183 unimplemented!("Not needed for this test")
184 }
185
186 fn clone_box(&self) -> Box<dyn ProtocolSim> {
187 Box::new(self.clone())
188 }
189
190 fn as_any(&self) -> &dyn Any {
191 self
192 }
193
194 fn as_any_mut(&mut self) -> &mut dyn Any {
195 self
196 }
197 fn eq(&self, _other: &dyn ProtocolSim) -> bool {
198 unimplemented!("Not needed for this test")
199 }
200 }
201
202 impl TryFromWithBlock<ComponentWithState, TimestampHeader> for DummyProtocol {
203 type Error = InvalidSnapshotError;
204 async fn try_from_with_header(
205 _value: ComponentWithState,
206 _header: TimestampHeader,
207 _account_balances: &HashMap<Bytes, HashMap<Bytes, Bytes>>,
208 _all_tokens: &HashMap<Bytes, Token>,
209 _decoder_context: &DecoderContext,
210 ) -> Result<Self, Self::Error> {
211 Ok(DummyProtocol)
212 }
213 }
214
215 pub struct MockRFQClient {
216 name: String,
217 interval: Duration,
218 error_at_time: Option<u128>,
219 }
220
221 impl MockRFQClient {
222 pub fn new(name: &str, interval: Duration, error_at_time: Option<u128>) -> Self {
223 Self { name: name.to_string(), interval, error_at_time }
224 }
225 }
226
227 #[async_trait]
228 impl RFQClient for MockRFQClient {
229 fn stream(
230 &self,
231 ) -> BoxStream<'static, Result<(String, StateSyncMessage<TimestampHeader>), RFQError>>
232 {
233 let name = self.name.clone();
234 let error_at_time = self.error_at_time;
235 let mut current_time: u128 = 0;
236 let interval = self.interval;
237 let interval =
238 IntervalStream::new(tokio::time::interval(self.interval)).map(move |_| {
239 if let Some(error_at_time) = error_at_time {
240 if error_at_time == current_time {
241 return Err(RFQError::FatalError(format!(
242 "{name} stream is dying and can't go on"
243 )));
244 };
245 };
246 let protocol_component =
247 ProtocolComponent { protocol_system: name.clone(), ..Default::default() };
248
249 let snapshot = Snapshot {
250 states: HashMap::from([(
251 name.clone(),
252 ComponentWithState {
253 state: ProtocolComponentState {
254 component_id: name.clone(),
255 attributes: HashMap::new(),
256 balances: HashMap::new(),
257 },
258 component: protocol_component,
259 component_tvl: None,
260 entrypoints: vec![],
261 },
262 )]),
263 vm_storage: HashMap::new(),
264 };
265
266 let msg = StateSyncMessage {
267 header: TimestampHeader { timestamp: current_time as u64 },
268 snapshots: snapshot,
269 ..Default::default()
270 };
271
272 current_time += interval.as_millis();
273 Ok((name.clone(), msg))
274 });
275 Box::pin(interval)
276 }
277
278 async fn request_binding_quote(
279 &self,
280 _params: &GetAmountOutParams,
281 ) -> Result<SignedQuote, RFQError> {
282 unimplemented!("Not needed for this test")
283 }
284 }
285
286 #[tokio::test]
287 async fn test_rfq_stream_builder() {
288 let (tx, mut rx) = mpsc::channel::<Update>(10);
292
293 let builder = RFQStreamBuilder::new()
294 .add_client::<DummyProtocol>(
295 "bebop",
296 Box::new(MockRFQClient::new("bebop", Duration::from_millis(100), Some(300))),
297 )
298 .add_client::<DummyProtocol>(
299 "hashflow",
300 Box::new(MockRFQClient::new("hashflow", Duration::from_millis(200), None)),
301 );
302
303 tokio::spawn(builder.build(tx));
304
305 let mut updates = Vec::new();
307 for _ in 0..6 {
308 let update = rx.recv().await.unwrap();
309 updates.push(update);
310 }
311
312 let bebop_updates: Vec<_> = updates
314 .iter()
315 .filter(|u| u.new_pairs.contains_key("bebop"))
316 .collect();
317 let hashflow_updates: Vec<_> = updates
318 .iter()
319 .filter(|u| u.new_pairs.contains_key("hashflow"))
320 .collect();
321
322 assert_eq!(bebop_updates[0].block_number_or_timestamp, 0,);
323 assert_eq!(hashflow_updates[0].block_number_or_timestamp, 0,);
324 assert_eq!(bebop_updates[1].block_number_or_timestamp, 100);
325 assert_eq!(bebop_updates[2].block_number_or_timestamp, 200);
326 assert_eq!(hashflow_updates[1].block_number_or_timestamp, 200);
327 assert_eq!(bebop_updates.len(), 3);
330 assert_eq!(hashflow_updates[2].block_number_or_timestamp, 400);
331 }
332}