1use std::{
2 collections::HashMap,
3 hash::{Hash, Hasher},
4};
5
6use deepsize::DeepSizeOf;
7use num_bigint::BigUint;
8use serde::{Deserialize, Serialize};
9
10use super::{Address, Balance};
11use crate::{dto::ResponseToken, models::Chain, traits::TokenOwnerFinding, Bytes};
12
13pub type TransferCost = u64;
15
16pub type TransferTax = u64;
18
19#[derive(Debug, Clone, Deserialize, Serialize, Eq, DeepSizeOf)]
20pub struct Token {
21 pub address: Bytes,
22 pub symbol: String,
23 pub decimals: u32,
24 pub tax: TransferTax,
25 pub gas: Vec<Option<TransferCost>>,
26 pub chain: Chain,
27 pub quality: u32,
35}
36
37impl Token {
38 pub fn new(
39 address: &Bytes,
40 symbol: &str,
41 decimals: u32,
42 tax: u64,
43 gas: &[Option<u64>],
44 chain: Chain,
45 quality: u32,
46 ) -> Self {
47 Self {
48 address: address.clone(),
49 symbol: symbol.to_string(),
50 decimals,
51 tax,
52 gas: gas.to_owned(),
53 chain,
54 quality,
55 }
56 }
57
58 pub fn one(&self) -> BigUint {
64 BigUint::from((1.0 * 10f64.powi(self.decimals as i32)) as u128)
65 }
66
67 pub fn gas_usage(&self) -> BigUint {
68 BigUint::from(
69 self.gas
70 .clone()
71 .into_iter()
72 .flatten()
73 .collect::<Vec<u64>>()
74 .iter()
75 .min()
76 .copied()
77 .unwrap_or(0u64),
78 )
79 }
80}
81
82impl PartialOrd for Token {
83 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
84 self.address.partial_cmp(&other.address)
85 }
86}
87
88impl PartialEq for Token {
89 fn eq(&self, other: &Self) -> bool {
90 self.address == other.address
91 }
92}
93
94impl Hash for Token {
95 fn hash<H: Hasher>(&self, state: &mut H) {
96 self.address.hash(state);
97 }
98}
99
100impl TryFrom<ResponseToken> for Token {
101 type Error = ();
102
103 fn try_from(value: ResponseToken) -> Result<Self, Self::Error> {
104 Ok(Self {
105 address: value.address,
106 decimals: value.decimals,
107 symbol: value.symbol.to_string(),
108 gas: value.gas,
109 chain: Chain::from(value.chain),
110 tax: value.tax,
111 quality: value.quality,
112 })
113 }
114}
115
116#[derive(Debug, Clone, Eq, PartialEq)]
126pub enum TokenQuality {
127 Good,
128 Bad { reason: String },
129}
130
131impl TokenQuality {
132 pub fn is_good(&self) -> bool {
133 matches!(self, Self::Good { .. })
134 }
135
136 pub fn bad(reason: impl ToString) -> Self {
137 Self::Bad { reason: reason.to_string() }
138 }
139}
140
141#[derive(Debug)]
154pub struct TokenOwnerStore {
155 values: HashMap<Address, (Address, Balance)>,
156}
157
158impl TokenOwnerStore {
159 pub fn new(values: HashMap<Address, (Address, Balance)>) -> Self {
160 TokenOwnerStore { values }
161 }
162}
163
164#[async_trait::async_trait]
165impl TokenOwnerFinding for TokenOwnerStore {
166 async fn find_owner(
167 &self,
168 token: Address,
169 _min_balance: Balance,
170 ) -> Result<Option<(Address, Balance)>, String> {
171 Ok(self.values.get(&token).cloned())
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use std::str::FromStr;
178
179 use super::*;
180
181 #[test]
182 fn test_constructor() {
183 let token = Token::new(
184 &Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
185 "USDC",
186 6,
187 1000,
188 &[Some(1000u64)],
189 Chain::Ethereum,
190 100,
191 );
192
193 assert_eq!(token.symbol, "USDC");
194 assert_eq!(token.decimals, 6);
195 assert_eq!(
196 format!("{token_address:#x}", token_address = token.address),
197 "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
198 );
199 }
200
201 #[test]
202 fn test_cmp() {
203 let usdc = Token::new(
204 &Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
205 "USDC",
206 6,
207 1000,
208 &[Some(1000u64)],
209 Chain::Ethereum,
210 100,
211 );
212 let usdc2 = Token::new(
213 &Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
214 "USDC2",
215 6,
216 1000,
217 &[Some(1000u64)],
218 Chain::Ethereum,
219 100,
220 );
221 let weth = Token::new(
222 &Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap(),
223 "WETH",
224 18,
225 1000,
226 &[Some(1000u64)],
227 Chain::Ethereum,
228 100,
229 );
230
231 assert!(usdc < weth);
232 assert_eq!(usdc, usdc2);
233 }
234
235 #[test]
236 fn test_one() {
237 let usdc = Token::new(
238 &Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
239 "USDC",
240 6,
241 1000,
242 &[Some(1000u64)],
243 Chain::Ethereum,
244 100,
245 );
246
247 assert_eq!(usdc.one(), BigUint::from(1000000u64));
248 }
249}