use core::fmt::Debug;
use std::{collections::HashMap, sync::Arc};
use async_trait::async_trait;
use crate::{
models::{
blockchain::{Block, BlockTag, EntryPointWithTracingParams, TracedEntryPoint},
contract::AccountDelta,
token::{Token, TokenQuality, TransferCost, TransferTax},
Address, Balance, BlockHash, StoreKey,
},
Bytes,
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct StorageSnapshotRequest {
pub address: Address,
pub slots: Option<Vec<StoreKey>>,
}
impl std::fmt::Display for StorageSnapshotRequest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let address_str = self.address.to_string();
let truncated_address = if address_str.len() >= 10 {
format!("{}...{}", &address_str[0..8], &address_str[address_str.len() - 4..])
} else {
address_str
};
match &self.slots {
Some(slots) => write!(f, "{truncated_address}[{} slots]", slots.len()),
None => write!(f, "{truncated_address}[all slots]"),
}
}
}
#[cfg_attr(feature = "test-utils", mockall::automock(type Error = String;))]
#[async_trait]
pub trait AccountExtractor {
type Error: Debug + Send + Sync;
async fn get_accounts_at_block(
&self,
block: &Block,
requests: &[StorageSnapshotRequest],
) -> Result<HashMap<Bytes, AccountDelta>, Self::Error>; }
#[async_trait]
pub trait TokenAnalyzer: Send + Sync {
type Error;
async fn analyze(
&self,
token: Bytes,
block: BlockTag,
) -> Result<(TokenQuality, Option<TransferCost>, Option<TransferTax>), Self::Error>;
}
#[async_trait]
pub trait TokenOwnerFinding: Send + Sync + Debug {
async fn find_owner(
&self,
token: Address,
min_balance: Balance,
) -> Result<Option<(Address, Balance)>, String>; }
#[async_trait]
pub trait TokenPreProcessor: Send + Sync {
async fn get_tokens(
&self,
addresses: Vec<Bytes>,
token_finder: Arc<dyn TokenOwnerFinding>,
block: BlockTag,
) -> Vec<Token>;
}
#[cfg_attr(feature = "test-utils", mockall::automock(type Error = String;))]
#[async_trait]
pub trait EntryPointTracer: Sync {
type Error: Debug;
async fn trace(
&self,
block_hash: BlockHash,
entry_points: Vec<EntryPointWithTracingParams>,
) -> Vec<Result<TracedEntryPoint, Self::Error>>;
}
#[cfg_attr(feature = "test-utils", mockall::automock(type Error = String;))]
#[async_trait]
pub trait BalanceSlotDetector: Send + Sync {
type Error: Debug;
async fn detect_balance_slots(
&self,
tokens: &[Address],
holder: Address,
block_hash: BlockHash,
) -> HashMap<Address, Result<(Address, Bytes), Self::Error>>;
}
#[cfg_attr(feature = "test-utils", mockall::automock(type Error = String;))]
#[async_trait]
pub trait AllowanceSlotDetector: Send + Sync {
type Error: Debug;
async fn detect_allowance_slots(
&self,
tokens: &[Address],
owner: Address,
spender: Address,
block_hash: BlockHash,
) -> HashMap<Address, Result<(Address, Bytes), Self::Error>>;
}
#[cfg_attr(feature = "test-utils", mockall::automock(type Error = String; type FeePrice = u128;))]
#[async_trait]
pub trait FeePriceGetter: Send + Sync {
type Error: Debug;
type FeePrice;
async fn get_latest_fee_price(&self) -> Result<Self::FeePrice, Self::Error>;
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
#[test]
fn test_storage_snapshot_request_display() {
let request_with_slots = StorageSnapshotRequest {
address: Address::from_str("0x1234567890123456789012345678901234567890").unwrap(),
slots: Some(vec![
StoreKey::from(vec![1, 2, 3, 4]),
StoreKey::from(vec![5, 6, 7, 8]),
StoreKey::from(vec![9, 10, 11, 12]),
]),
};
let display_output = request_with_slots.to_string();
assert_eq!(display_output, "0x123456...7890[3 slots]");
let request_all_slots = StorageSnapshotRequest {
address: Address::from_str("0x9876543210987654321098765432109876543210").unwrap(),
slots: None,
};
let display_output = request_all_slots.to_string();
assert_eq!(display_output, "0x987654...3210[all slots]");
let request_empty_slots = StorageSnapshotRequest {
address: Address::from_str("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd").unwrap(),
slots: Some(vec![]),
};
let display_output = request_empty_slots.to_string();
assert_eq!(display_output, "0xabcdef...abcd[0 slots]");
}
}