tycho_simulation/rfq/protocols/native/
all_pairs_decoder.rs1use std::collections::HashMap;
2
3use tycho_client::feed::synchronizer::ComponentWithState;
4use tycho_common::{models::token::Token, Bytes};
5
6use super::{
7 all_pairs_state::NativeAllPairsState, client_builder::NativeClientBuilder,
8 models::NativePriceData,
9};
10use crate::{
11 protocol::{
12 errors::InvalidSnapshotError,
13 models::{DecoderContext, TryFromWithBlock},
14 },
15 rfq::{
16 models::{ComponentLayout, QuoteRule, TimestampHeader},
17 protocols::component::{decode_all_pairs_component, DecodedAllPairs},
18 },
19};
20
21impl TryFromWithBlock<ComponentWithState, TimestampHeader> for NativeAllPairsState {
22 type Error = InvalidSnapshotError;
23
24 async fn try_from_with_header(
25 snapshot: ComponentWithState,
26 _timestamp_header: TimestampHeader,
27 _account_balances: &HashMap<Bytes, HashMap<Bytes, Bytes>>,
28 all_tokens: &HashMap<Bytes, Token>,
29 _decoder_context: &DecoderContext,
30 ) -> Result<Self, Self::Error> {
31 let DecodedAllPairs { books, tokens, quote_rule } =
32 decode_all_pairs_component::<NativePriceData>(&snapshot, all_tokens)?;
33 if quote_rule.is_some_and(|rule| rule != QuoteRule::OncePerVenue) {
34 return Err(InvalidSnapshotError::ValueError(
35 "Native names no market maker; its quote rule is once_per_venue".into(),
36 ));
37 }
38
39 let client = NativeClientBuilder::from_env(snapshot.component.chain)
40 .map_err(|e| {
41 InvalidSnapshotError::ValueError(format!(
42 "Failed to get Native Relay authentication: {e}"
43 ))
44 })?
45 .tokens(tokens.keys().cloned().collect())
46 .component_layout(ComponentLayout::AllPairs)
47 .build()
48 .map_err(|e| {
49 InvalidSnapshotError::ValueError(format!("Couldn't create NativeClient: {e}"))
50 })?;
51
52 Ok(NativeAllPairsState::new(books, tokens, client))
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use std::env;
59
60 use num_bigint::BigUint;
61 use tycho_common::simulation::protocol_sim::ProtocolSim;
62
63 use super::*;
64 use crate::rfq::protocols::{
65 native::models::NativePriceLevel,
66 test_utils::{all_pairs_snapshot, decode, usdc, wbtc, weth},
67 };
68
69 fn book(base: &Token, quote: &Token, bid: f64, ask: f64) -> NativePriceData {
70 NativePriceData {
71 base_address: base.address.clone(),
72 quote_address: quote.address.clone(),
73 minimum_in_base: 0.0,
74 minimum_in_quote: 0.0,
75 minimum_out_base: 0.0,
76 minimum_out_quote: 0.0,
77 bids: vec![NativePriceLevel { quantity: 1.5, price: bid }],
78 asks: vec![NativePriceLevel { quantity: 2.0, price: ask }],
79 }
80 }
81
82 fn snapshot() -> (ComponentWithState, HashMap<Bytes, Token>) {
83 env::set_var("NATIVE_API_KEY", "test_key");
84 let books =
85 vec![book(&weth(), &usdc(), 3000.0, 3010.0), book(&wbtc(), &usdc(), 65000.0, 65100.0)];
86 all_pairs_snapshot("rfq:native", &[weth(), usdc(), wbtc()], &books)
87 }
88
89 #[tokio::test]
90 async fn test_decodes_books() {
91 let (snapshot, tokens) = snapshot();
92 let state = decode::<NativeAllPairsState>(snapshot, &tokens)
93 .await
94 .unwrap();
95
96 assert_eq!(
97 state
98 .spot_price(&weth(), &usdc())
99 .unwrap(),
100 3005.0
101 );
102 assert_eq!(
103 state
104 .spot_price(&wbtc(), &usdc())
105 .unwrap(),
106 65050.0
107 );
108 let (sell_limit, _) = state
109 .get_limits(weth().address, usdc().address)
110 .unwrap();
111 assert_eq!(sell_limit, BigUint::from(1_500_000_000_000_000_000u64));
112 }
113
114 #[tokio::test]
115 async fn test_once_per_maker_attribute() {
116 let (mut snapshot, tokens) = snapshot();
117 snapshot
118 .component
119 .static_attributes
120 .insert(QuoteRule::ATTRIBUTE.to_string(), b"once_per_maker".into());
121 let result = decode::<NativeAllPairsState>(snapshot, &tokens).await;
122 assert!(
123 matches!(result.unwrap_err(), InvalidSnapshotError::ValueError(msg) if msg.contains("names no market maker"))
124 );
125 }
126}