debot_ether_ether_utils/dex/
dex.rs

1// dex.rs
2
3use crate::token::Token;
4use async_trait::async_trait;
5use ethers::prelude::LocalWallet;
6use ethers::utils::parse_units;
7use ethers::{
8    abi::Abi,
9    prelude::*,
10    types::{Address, U256},
11};
12use std::{error::Error, sync::Arc};
13
14use ethers::core::types::Log;
15
16use anyhow::Error as AnyhowError;
17
18fn parse_swap_log(log: &Log) -> Result<(f64, f64, f64, f64), AnyhowError> {
19    // Check if this log has the correct number of topics and the data
20    if log.topics.len() < 2 || log.data.is_empty() {
21        return Err(AnyhowError::msg(
22            "Log does not have enough topics/data for parsing swap",
23        ));
24    }
25
26    // Convert topics to U256
27    let amount0_in = U256::from(log.topics[0].as_fixed_bytes());
28    let amount1_in = U256::from(log.topics[1].as_fixed_bytes());
29
30    // Convert log.data to byte slice and then slice it
31    let data = log.data.as_ref();
32    let amount0_out = U256::from_big_endian(&data[0..32]);
33    let amount1_out = U256::from_big_endian(&data[32..64]);
34
35    // Convert U256 amounts to f64
36    let amount0_in = amount0_in.low_u64() as f64;
37    let amount1_in = amount1_in.low_u64() as f64;
38    let amount0_out = amount0_out.low_u64() as f64;
39    let amount1_out = amount1_out.low_u64() as f64;
40
41    Ok((amount0_in, amount1_in, amount0_out, amount1_out))
42}
43
44#[derive(Debug, Clone)]
45pub struct BaseDex {
46    pub provider: Arc<NonceManagerMiddleware<SignerMiddleware<Provider<Http>, LocalWallet>>>,
47    pub router_address: Address,
48    router_contract:
49        Option<Contract<NonceManagerMiddleware<SignerMiddleware<Provider<Http>, LocalWallet>>>>,
50}
51
52impl BaseDex {
53    pub fn new(
54        provider: Arc<NonceManagerMiddleware<SignerMiddleware<Provider<Http>, LocalWallet>>>,
55        router_address: Address,
56    ) -> Self {
57        Self {
58            provider: provider,
59            router_address: router_address,
60            router_contract: None,
61        }
62    }
63
64    pub async fn create_router_contract(
65        &mut self,
66        abi_json: &[u8],
67    ) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
68        if self.router_contract.is_none() {
69            let router_abi = Abi::load(abi_json)?;
70            let router_contract =
71                Contract::new(self.router_address, router_abi, self.provider.clone());
72            self.router_contract = Some(router_contract);
73        }
74        Ok(())
75    }
76
77    pub fn provider(
78        &self,
79    ) -> Arc<NonceManagerMiddleware<SignerMiddleware<Provider<Http>, LocalWallet>>> {
80        self.provider.clone()
81    }
82
83    pub fn router_address(&self) -> Address {
84        self.router_address
85    }
86
87    pub fn router_contract(
88        &self,
89    ) -> Result<
90        &Contract<NonceManagerMiddleware<SignerMiddleware<Provider<Http>, LocalWallet>>>,
91        Box<dyn Error + Send + Sync + 'static>,
92    > {
93        match &self.router_contract {
94            Some(contract) => Ok(contract),
95            None => Err(Box::new(std::io::Error::new(
96                std::io::ErrorKind::Other,
97                "Router contract not created",
98            ))),
99        }
100    }
101}
102
103#[derive(Clone)]
104pub struct TokenPair {
105    input_token: Arc<Box<dyn Token>>,
106    output_token: Arc<Box<dyn Token>>,
107}
108
109impl TokenPair {
110    pub fn new(input_token: Arc<Box<dyn Token>>, output_token: Arc<Box<dyn Token>>) -> Self {
111        TokenPair {
112            input_token,
113            output_token,
114        }
115    }
116
117    pub fn swap(self) -> Self {
118        TokenPair {
119            input_token: self.input_token,
120            output_token: self.output_token,
121        }
122    }
123}
124
125#[async_trait]
126pub trait Dex: Send + Sync {
127    async fn get_token_price(
128        &self,
129        token_pair: &TokenPair,
130        amount: f64,
131        use_get_amounts_in: bool,
132    ) -> Result<f64, Box<dyn std::error::Error + Send + Sync + 'static>> {
133        let input_address = token_pair.input_token.address();
134        let output_address = token_pair.output_token.address();
135
136        let input_decimals = token_pair.input_token.decimals().unwrap();
137        let output_decimals = token_pair.output_token.decimals().unwrap();
138
139        let router_contract = self.router_contract().unwrap();
140
141        let mut amount_in = U256::from_dec_str(&format!(
142            "{:.0}",
143            amount * 10f64.powi(input_decimals as i32)
144        ))?;
145
146        let mut amount_out = U256::from_dec_str(&format!(
147            "{:.0}",
148            amount * 10f64.powi(output_decimals as i32)
149        ))?;
150
151        if use_get_amounts_in {
152            let amounts_in: Vec<U256> = router_contract
153                .method::<_, Vec<U256>>(
154                    "getAmountsIn",
155                    (amount_out, vec![input_address, output_address]),
156                )?
157                .call()
158                .await?;
159            amount_in = amounts_in[0];
160        } else {
161            let amounts_out: Vec<U256> = router_contract
162                .method::<_, Vec<U256>>(
163                    "getAmountsOut",
164                    (amount_in, vec![input_address, output_address]),
165                )?
166                .call()
167                .await?;
168            amount_out = amounts_out[1];
169        }
170
171        let price_f64 = amount_out.as_u128() as f64 / amount_in.as_u128() as f64
172            * 10f64.powi(input_decimals as i32 - output_decimals as i32);
173
174        log::trace!(
175            "{}, Amount-in: {}({}), Amount-out: {}({}), Price: {:6.6}",
176            self.name(),
177            amount_in,
178            token_pair.input_token.symbol_name(),
179            amount_out,
180            token_pair.output_token.symbol_name(),
181            price_f64
182        );
183
184        Ok(price_f64)
185    }
186
187    async fn swap_token(
188        &self,
189        token_pair: &TokenPair,
190        amount: f64,
191        wallet_and_provider: Arc<
192            NonceManagerMiddleware<SignerMiddleware<Provider<Http>, LocalWallet>>,
193        >,
194        address: Address,
195        deadline_secs: u64,
196    ) -> Result<f64, Box<dyn std::error::Error + Send + Sync + 'static>> {
197        let input_address = token_pair.input_token.address();
198        let output_address = token_pair.output_token.address();
199
200        let input_decimals = token_pair.input_token.decimals().unwrap();
201        let amount_in = U256::from_dec_str(&format!(
202            "{:.0}",
203            amount * 10f64.powi(input_decimals as i32)
204        ))?;
205
206        let router_contract = self.router_contract().unwrap();
207
208        let deadline = U256::from(
209            std::time::SystemTime::now()
210                .duration_since(std::time::UNIX_EPOCH)?
211                .as_secs()
212                + deadline_secs,
213        );
214
215        let connected_contract = router_contract.connect(wallet_and_provider.clone());
216
217        let method_call = connected_contract.method::<_, bool>(
218            "swapExactTokensForTokens",
219            (
220                amount_in,
221                U256::zero(),
222                vec![input_address, output_address],
223                address,
224                deadline,
225            ),
226        )?;
227
228        let swap_transaction = method_call.send().await?;
229
230        let transaction_receipt = swap_transaction.confirmations(1).await?; // wait for 1 confirmation
231
232        let transaction_receipt = match transaction_receipt {
233            Some(receipt) => receipt,
234            None => {
235                return Err(Box::new(std::io::Error::new(
236                    std::io::ErrorKind::Other,
237                    "Transaction receipt is none",
238                )))
239            }
240        };
241
242        if transaction_receipt.status != Some(1.into()) {
243            return Err(Box::new(std::io::Error::new(
244                std::io::ErrorKind::Other,
245                "Token swap transaction failed",
246            )));
247        }
248
249        // Parse the Swap event logs to get the output amount
250        let logs = transaction_receipt.logs;
251
252        let mut output_amount: Option<U256> = None;
253
254        for log in &logs {
255            if log.address == output_address {
256                let (_amount0_in, _amount1_in, amount0_out, amount1_out) = parse_swap_log(&log)?;
257                output_amount = Some(parse_units(&(amount0_out + amount1_out), 18)?.into());
258                break;
259            }
260        }
261
262        let output_amount = output_amount.ok_or_else(|| {
263            Box::new(std::io::Error::new(
264                std::io::ErrorKind::Other,
265                "Output amount not found in transaction logs",
266            ))
267        })?;
268
269        let output_decimals = token_pair.output_token.decimals().unwrap();
270        let output_amount_in_token =
271            output_amount.low_u64() as f64 / 10f64.powi(output_decimals as i32);
272
273        Ok(output_amount_in_token)
274    }
275
276    async fn has_token_pair(&self, input_token: &dyn Token, output_token: &dyn Token) -> bool {
277        let input_address = input_token.address();
278        let output_address = output_token.address();
279
280        let router_contract = match self.router_contract() {
281            Ok(contract) => contract,
282            Err(_) => return false, // Return false if the router contract is not created
283        };
284
285        let connected_contract = router_contract.connect(self.provider().clone());
286
287        let method_call = connected_contract.method::<_, Vec<U256>>(
288            "getAmountsOut",
289            (U256::one(), vec![input_address, output_address]),
290        );
291
292        let amounts_out = match method_call
293            .expect("Failed to execute contract call")
294            .call()
295            .await
296        {
297            Ok(result) => result,
298            Err(err) => {
299                // Handle the error or panic with a custom message
300                log::error!("Failed to execute contract call: {:?}", err);
301                return false;
302            }
303        };
304
305        if let Some(output_amount) = amounts_out.get(1) {
306            !output_amount.is_zero()
307        } else {
308            false
309        }
310    }
311
312    async fn initialize(&mut self) -> Result<(), Box<dyn Error + Send + Sync>>;
313    fn clone_box(&self) -> Box<dyn Dex + Send + Sync>;
314    fn name(&self) -> &str;
315    fn provider(
316        &self,
317    ) -> Arc<NonceManagerMiddleware<SignerMiddleware<Provider<Http>, LocalWallet>>>;
318    fn router_address(&self) -> Address;
319    fn router_contract(
320        &self,
321    ) -> Result<
322        &Contract<NonceManagerMiddleware<SignerMiddleware<Provider<Http>, LocalWallet>>>,
323        Box<dyn Error + Send + Sync + 'static>,
324    >;
325}
326
327impl Clone for Box<dyn Dex> {
328    fn clone(&self) -> Box<dyn Dex> {
329        self.clone_box()
330    }
331}
332
333impl PartialEq for Box<dyn Dex> {
334    fn eq(&self, other: &Self) -> bool {
335        std::ptr::eq(self.as_ref(), other.as_ref())
336    }
337}