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)]
154pub struct TokenOwnerStore {
155 values: HashMap<Address, (Address, Balance)>,
158}
159
160impl TokenOwnerStore {
161 pub fn new(values: HashMap<Address, (Address, Balance)>) -> Self {
162 TokenOwnerStore { values }
163 }
164}
165
166#[async_trait::async_trait]
167impl TokenOwnerFinding for TokenOwnerStore {
168 async fn find_owner(
169 &self,
170 token: Address,
171 _min_balance: Balance,
172 ) -> Result<Option<(Address, Balance)>, String> {
173 Ok(self.values.get(&token).cloned())
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use std::str::FromStr;
180
181 use super::*;
182
183 #[test]
184 fn test_constructor() {
185 let token = Token::new(
186 &Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
187 "USDC",
188 6,
189 1000,
190 &[Some(1000u64)],
191 Chain::Ethereum,
192 100,
193 );
194
195 assert_eq!(token.symbol, "USDC");
196 assert_eq!(token.decimals, 6);
197 assert_eq!(
198 format!("{token_address:#x}", token_address = token.address),
199 "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
200 );
201 }
202
203 #[test]
204 fn test_cmp() {
205 let usdc = Token::new(
206 &Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
207 "USDC",
208 6,
209 1000,
210 &[Some(1000u64)],
211 Chain::Ethereum,
212 100,
213 );
214 let usdc2 = Token::new(
215 &Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
216 "USDC2",
217 6,
218 1000,
219 &[Some(1000u64)],
220 Chain::Ethereum,
221 100,
222 );
223 let weth = Token::new(
224 &Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap(),
225 "WETH",
226 18,
227 1000,
228 &[Some(1000u64)],
229 Chain::Ethereum,
230 100,
231 );
232
233 assert!(usdc < weth);
234 assert_eq!(usdc, usdc2);
235 }
236
237 #[test]
238 fn test_one() {
239 let usdc = Token::new(
240 &Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
241 "USDC",
242 6,
243 1000,
244 &[Some(1000u64)],
245 Chain::Ethereum,
246 100,
247 );
248
249 assert_eq!(usdc.one(), BigUint::from(1000000u64));
250 }
251}