tycho_common/traits.rs
1use core::fmt::Debug;
2use std::{collections::HashMap, sync::Arc};
3
4use async_trait::async_trait;
5
6use crate::{
7 models::{
8 blockchain::{Block, BlockTag, EntryPointWithTracingParams, TracedEntryPoint},
9 contract::AccountDelta,
10 token::{Token, TokenQuality, TransferCost, TransferTax},
11 Address, Balance, BlockHash, StoreKey,
12 },
13 Bytes,
14};
15
16/// A struct representing a request to get an account state.
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub struct StorageSnapshotRequest {
19 // The address of the account to get the state of.
20 pub address: Address,
21 // The specific slots to get the state of. If `None`, the entire account state will be
22 // returned.
23 pub slots: Option<Vec<StoreKey>>,
24}
25
26impl std::fmt::Display for StorageSnapshotRequest {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 let address_str = self.address.to_string();
29 let truncated_address = if address_str.len() >= 10 {
30 format!("{}...{}", &address_str[0..8], &address_str[address_str.len() - 4..])
31 } else {
32 address_str
33 };
34
35 match &self.slots {
36 Some(slots) => write!(f, "{truncated_address}[{} slots]", slots.len()),
37 None => write!(f, "{truncated_address}[all slots]"),
38 }
39 }
40}
41
42/// Trait for getting multiple account states from chain data.
43#[cfg_attr(feature = "test-utils", mockall::automock(type Error = String;))]
44#[async_trait]
45pub trait AccountExtractor {
46 type Error: Debug + Send + Sync;
47
48 /// Get the account states at the end of the given block (after all transactions in the block
49 /// have been applied).
50 ///
51 /// # Arguments
52 ///
53 /// * `block`: The block at which to retrieve the account states.
54 /// * `requests`: A slice of `StorageSnapshotRequest` objects, each containing an address and
55 /// optional slots.
56 /// Note: If the `slots` field is `None`, the function will return the entire account state.
57 /// That could be a lot of data, so use with caution.
58 ///
59 /// returns: Result<HashMap<Bytes, AccountDelta, RandomState>, Self::Error>
60 /// A result containing a HashMap where the keys are `Bytes` (addresses) and the values are
61 /// `AccountDelta` objects.
62 async fn get_accounts_at_block(
63 &self,
64 block: &Block,
65 requests: &[StorageSnapshotRequest],
66 ) -> Result<HashMap<Bytes, AccountDelta>, Self::Error>; //TODO: do not return `AccountUpdate` but `Account`
67}
68
69/// Trait for analyzing a token, including its quality, transfer cost, and transfer tax.
70#[async_trait]
71pub trait TokenAnalyzer: Send + Sync {
72 type Error;
73
74 /// Analyzes the quality of a token given its address and a block tag.
75 ///
76 /// # Parameters
77 /// * `token` - The address of the token to analyze.
78 /// * `block` - The block tag at which the analysis should be performed.
79 ///
80 /// # Returns
81 /// A result containing:
82 /// * `TokenQuality` - The quality assessment of the token (either `Good` or `Bad`).
83 /// * `Option<TransferCost>` - The average cost per transfer, if available.
84 /// * `Option<TransferTax>` - The transfer tax, if applicable.
85 ///
86 /// On failure, returns `Self::Error`.
87 async fn analyze(
88 &self,
89 token: Bytes,
90 block: BlockTag,
91 ) -> Result<(TokenQuality, Option<TransferCost>, Option<TransferTax>), Self::Error>;
92}
93
94/// Trait for finding an address that owns a specific token. This is useful for detecting
95/// bad tokens by identifying addresses with enough balance to simulate transactions.
96#[async_trait]
97pub trait TokenOwnerFinding: Send + Sync + Debug {
98 /// Finds an address that holds at least `min_balance` of the specified token.
99 ///
100 /// # Parameters
101 /// * `token` - The address of the token to search for.
102 /// * `min_balance` - The minimum balance required for the address to be considered.
103 ///
104 /// # Returns
105 /// A result containing:
106 /// * `Option<(Address, Balance)>` - The address and its actual balance if an owner is found.
107 /// If no address meets the criteria, returns `None`.
108 /// On failure, returns a string representing an error message.
109 async fn find_owner(
110 &self,
111 token: Address,
112 min_balance: Balance,
113 ) -> Result<Option<(Address, Balance)>, String>; // TODO: introduce custom error type
114}
115
116/// Trait for retrieving additional information about tokens, such as the number of decimals
117/// and the token symbol, to help construct `CurrencyToken` objects.
118#[async_trait]
119pub trait TokenPreProcessor: Send + Sync {
120 /// Given a list of token addresses, this function retrieves additional metadata for each token.
121 ///
122 /// # Parameters
123 /// * `addresses` - A vector of token addresses to process.
124 /// * `token_finder` - A reference to a `TokenOwnerFinding` implementation to help find token
125 /// owners.
126 /// * `block` - The block tag at which the information should be retrieved.
127 ///
128 /// # Returns
129 /// A vector of `CurrencyToken` objects, each containing the processed information for the
130 /// token.
131 async fn get_tokens(
132 &self,
133 addresses: Vec<Bytes>,
134 token_finder: Arc<dyn TokenOwnerFinding>,
135 block: BlockTag,
136 ) -> Vec<Token>;
137}
138
139/// Trait for tracing blockchain transaction execution.
140#[cfg_attr(feature = "test-utils", mockall::automock(type Error = String;))]
141#[async_trait]
142pub trait EntryPointTracer: Sync {
143 type Error: Debug;
144
145 /// Traces the execution of a list of entry points at a specific block.
146 ///
147 /// # Parameters
148 /// * `block_hash` - The hash of the block at which to perform the trace. The trace will use the
149 /// state of the blockchain at this block.
150 /// * `entry_points` - A list of entry points to trace with their data.
151 ///
152 /// # Returns
153 /// Returns a vector of `TracedEntryPoint`, where each element contains:
154 /// * `retriggers` - A set of (address, storage slot) pairs representing storage locations that
155 /// could alter tracing results. If any of these storage slots change, the set of called
156 /// contract might be outdated.
157 /// * `accessed_slots` - A map of all contract addresses that were called during the trace with
158 /// a list of storage slots that were accessed (read or written).
159 async fn trace(
160 &self,
161 block_hash: BlockHash,
162 entry_points: Vec<EntryPointWithTracingParams>,
163 ) -> Vec<Result<TracedEntryPoint, Self::Error>>;
164}
165
166/// Trait for detecting storage slots that contain ERC20 token balances
167#[cfg_attr(feature = "test-utils", mockall::automock(type Error = String;))]
168#[async_trait]
169pub trait BalanceSlotDetector: Send + Sync {
170 type Error: Debug;
171
172 /// Detect balance storage slots for multiple tokens from a single holder.
173 /// Useful to allow overriding balances.
174 ///
175 /// # Arguments
176 /// * `tokens` - Slice of ERC20 token addresses.
177 /// * `holder` - Address that holds the tokens (e.g., pool manager)
178 /// * `block_hash` - Block at which to detect slots
179 ///
180 /// # Returns
181 /// HashMap mapping Token -> Result containing (contract_address -> storage_slot) or error.
182 /// The storage slot is the one that controls the token's holder balance.
183 async fn detect_balance_slots(
184 &self,
185 tokens: &[Address],
186 holder: Address,
187 block_hash: BlockHash,
188 ) -> HashMap<Address, Result<(Address, Bytes), Self::Error>>;
189}
190
191/// Trait for calculating the memory weight/size of values for caching purposes
192pub trait MemorySize {
193 /// Returns the approximate memory size in bytes
194 fn memory_size(&self) -> usize;
195}
196
197/// Trait for detecting storage slots that contain ERC20 token allowances
198#[cfg_attr(feature = "test-utils", mockall::automock(type Error = String;))]
199#[async_trait]
200pub trait AllowanceSlotDetector: Send + Sync {
201 type Error: Debug;
202
203 /// Detect allowance storage slots for multiple tokens for owner-spender pairs.
204 /// Useful to allow overriding allowances in simulations.
205 ///
206 /// # Arguments
207 /// * `tokens` - Slice of ERC20 token addresses.
208 /// * `owner` - Address that owns the tokens
209 /// * `spender` - Address that is allowed to spend the tokens
210 /// * `block_hash` - Block at which to detect slots
211 ///
212 /// # Returns
213 /// HashMap mapping Token -> Result containing (contract_address -> storage_slot) or error.
214 /// The storage slot is the one that controls the allowance from owner to spender.
215 async fn detect_allowance_slots(
216 &self,
217 tokens: &[Address],
218 owner: Address,
219 spender: Address,
220 block_hash: BlockHash,
221 ) -> HashMap<Address, Result<(Address, Bytes), Self::Error>>;
222}
223
224#[cfg(test)]
225mod tests {
226 use std::str::FromStr;
227
228 use super::*;
229
230 #[test]
231 fn test_storage_snapshot_request_display() {
232 // Test with specific slots
233 let request_with_slots = StorageSnapshotRequest {
234 address: Address::from_str("0x1234567890123456789012345678901234567890").unwrap(),
235 slots: Some(vec![
236 StoreKey::from(vec![1, 2, 3, 4]),
237 StoreKey::from(vec![5, 6, 7, 8]),
238 StoreKey::from(vec![9, 10, 11, 12]),
239 ]),
240 };
241
242 let display_output = request_with_slots.to_string();
243 assert_eq!(display_output, "0x123456...7890[3 slots]");
244
245 // Test with all slots
246 let request_all_slots = StorageSnapshotRequest {
247 address: Address::from_str("0x9876543210987654321098765432109876543210").unwrap(),
248 slots: None,
249 };
250
251 let display_output = request_all_slots.to_string();
252 assert_eq!(display_output, "0x987654...3210[all slots]");
253
254 // Test with empty slots vector
255 let request_empty_slots = StorageSnapshotRequest {
256 address: Address::from_str("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd").unwrap(),
257 slots: Some(vec![]),
258 };
259
260 let display_output = request_empty_slots.to_string();
261 assert_eq!(display_output, "0xabcdef...abcd[0 slots]");
262 }
263}