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