Skip to main content

tycho_simulation/rfq/protocols/native/
all_pairs_decoder.rs

1use 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::MissingAttribute(format!("Couldn't create NativeClient: {e}"))
50            })?;
51
52        NativeAllPairsState::new(books, tokens, client)
53            .map_err(|e| InvalidSnapshotError::ValueError(e.to_string()))
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use std::env;
60
61    use num_bigint::BigUint;
62    use tycho_common::simulation::protocol_sim::ProtocolSim;
63
64    use super::*;
65    use crate::rfq::protocols::{
66        native::models::NativePriceLevel,
67        test_utils::{all_pairs_snapshot, decode, usdc, wbtc, weth},
68    };
69
70    fn book(base: &Token, quote: &Token, bid: f64, ask: f64) -> NativePriceData {
71        NativePriceData {
72            base_address: base.address.clone(),
73            quote_address: quote.address.clone(),
74            minimum_in_base: 0.0,
75            minimum_in_quote: 0.0,
76            minimum_out_base: 0.0,
77            minimum_out_quote: 0.0,
78            bids: vec![NativePriceLevel { quantity: 1.5, price: bid }],
79            asks: vec![NativePriceLevel { quantity: 2.0, price: ask }],
80        }
81    }
82
83    fn snapshot() -> (ComponentWithState, HashMap<Bytes, Token>) {
84        env::set_var("NATIVE_API_KEY", "test_key");
85        let books =
86            vec![book(&weth(), &usdc(), 3000.0, 3010.0), book(&wbtc(), &usdc(), 65000.0, 65100.0)];
87        all_pairs_snapshot("rfq:native", &[weth(), usdc(), wbtc()], &books)
88    }
89
90    #[tokio::test]
91    async fn test_decodes_books() {
92        let (snapshot, tokens) = snapshot();
93        let state = decode::<NativeAllPairsState>(snapshot, &tokens)
94            .await
95            .unwrap();
96
97        assert_eq!(
98            state
99                .spot_price(&weth(), &usdc())
100                .unwrap(),
101            3005.0
102        );
103        assert_eq!(
104            state
105                .spot_price(&wbtc(), &usdc())
106                .unwrap(),
107            65050.0
108        );
109        let (sell_limit, _) = state
110            .get_limits(weth().address, usdc().address)
111            .unwrap();
112        assert_eq!(sell_limit, BigUint::from(1_500_000_000_000_000_000u64));
113    }
114
115    #[tokio::test]
116    async fn test_once_per_maker_attribute() {
117        let (mut snapshot, tokens) = snapshot();
118        snapshot
119            .component
120            .static_attributes
121            .insert(QuoteRule::ATTRIBUTE.to_string(), b"once_per_maker".into());
122        let result = decode::<NativeAllPairsState>(snapshot, &tokens).await;
123        assert!(
124            matches!(result.unwrap_err(), InvalidSnapshotError::ValueError(msg) if msg.contains("names no market maker"))
125        );
126    }
127}