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