debot_ether_ether_utils/dex/
dex.rs1use 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 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 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 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 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?; 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 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, };
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 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}