Skip to main content

tycho_common/models/
mod.rs

1pub mod blockchain;
2pub mod contract;
3pub mod error;
4pub mod protocol;
5pub mod token;
6
7use std::{collections::HashMap, fmt::Display, str::FromStr};
8
9pub use blockchain::{BlockChanges, TxWithContractChanges};
10use deepsize::DeepSizeOf;
11use serde::{Deserialize, Serialize};
12use strum_macros::{Display, EnumString};
13use thiserror::Error;
14use token::Token;
15
16use crate::{dto, Bytes};
17
18/// Address hash literal type to uniquely identify contracts/accounts on a
19/// blockchain.
20pub type Address = Bytes;
21
22/// Block hash literal type to uniquely identify a block in the chain and
23/// likely across chains.
24pub type BlockHash = Bytes;
25
26/// Transaction hash literal type to uniquely identify a transaction in the
27/// chain and likely across chains.
28pub type TxHash = Bytes;
29
30/// Smart contract code is represented as a byte vector containing opcodes.
31pub type Code = Bytes;
32
33/// The hash of a contract's code is used to identify it.
34pub type CodeHash = Bytes;
35
36/// The balance of an account is a big endian serialised integer of variable size.
37pub type Balance = Bytes;
38
39/// Key literal type of the contract store.
40pub type StoreKey = Bytes;
41
42/// Key literal type of the attribute store.
43pub type AttrStoreKey = String;
44
45/// Value literal type of the contract store.
46pub type StoreVal = Bytes;
47
48/// A binary key-value store for an account.
49pub type ContractStore = HashMap<StoreKey, StoreVal>;
50pub type ContractStoreDeltas = HashMap<StoreKey, Option<StoreVal>>;
51pub type AccountToContractStoreDeltas = HashMap<Address, ContractStoreDeltas>;
52
53/// Component id literal type to uniquely identify a component.
54pub type ComponentId = String;
55
56/// Protocol system literal type to uniquely identify a protocol system.
57pub type ProtocolSystem = String;
58
59/// Entry point id literal type to uniquely identify an entry point.
60pub type EntryPointId = String;
61
62/// TVL threshold tiers for chain-aware filtering defaults.
63///
64/// TVL is denominated in each chain's native token. Since native tokens have different USD values,
65/// the same numeric threshold produces wildly different USD-equivalent filters across chains.
66/// These tiers provide sensible defaults targeting equivalent USD values.
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
68pub enum TvlThresholdTier {
69    /// Filters out dust pools (~$20K USD equivalent in native token).
70    Low,
71    /// Filters for pools with meaningful liquidity (~$200K USD equivalent in native token).
72    Medium,
73}
74
75#[derive(
76    Debug,
77    Clone,
78    Copy,
79    PartialEq,
80    Eq,
81    Hash,
82    Serialize,
83    Deserialize,
84    EnumString,
85    Display,
86    Default,
87    DeepSizeOf,
88)]
89#[serde(rename_all = "lowercase")]
90#[strum(serialize_all = "lowercase")]
91pub enum Chain {
92    #[default]
93    Ethereum,
94    Starknet,
95    ZkSync,
96    Arbitrum,
97    Base,
98    Bsc,
99    Unichain,
100    Polygon,
101}
102
103impl From<dto::Chain> for Chain {
104    fn from(value: dto::Chain) -> Self {
105        match value {
106            dto::Chain::Ethereum => Chain::Ethereum,
107            dto::Chain::Starknet => Chain::Starknet,
108            dto::Chain::ZkSync => Chain::ZkSync,
109            dto::Chain::Arbitrum => Chain::Arbitrum,
110            dto::Chain::Base => Chain::Base,
111            dto::Chain::Bsc => Chain::Bsc,
112            dto::Chain::Unichain => Chain::Unichain,
113            dto::Chain::Polygon => Chain::Polygon,
114        }
115    }
116}
117
118impl From<dto::ChangeType> for ChangeType {
119    fn from(value: dto::ChangeType) -> Self {
120        match value {
121            dto::ChangeType::Update => ChangeType::Update,
122            dto::ChangeType::Creation => ChangeType::Creation,
123            dto::ChangeType::Deletion => ChangeType::Deletion,
124            dto::ChangeType::Unspecified => ChangeType::Update,
125        }
126    }
127}
128
129fn native_eth(chain: Chain) -> Token {
130    Token::new(
131        &Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
132        "ETH",
133        18,
134        0,
135        &[Some(2300)],
136        chain,
137        100,
138    )
139}
140
141fn native_bsc(chain: Chain) -> Token {
142    Token::new(
143        &Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
144        "BNB",
145        18,
146        0,
147        &[Some(2300)],
148        chain,
149        100,
150    )
151}
152
153fn wrapped_native_eth(chain: Chain, address: &str) -> Token {
154    Token::new(&Bytes::from_str(address).unwrap(), "WETH", 18, 0, &[Some(2300)], chain, 100)
155}
156
157fn native_pol(chain: Chain) -> Token {
158    Token::new(
159        &Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
160        "POL",
161        18,
162        0,
163        &[Some(2300)],
164        chain,
165        100,
166    )
167}
168
169fn wrapped_native_bsc(chain: Chain, address: &str) -> Token {
170    Token::new(&Bytes::from_str(address).unwrap(), "WBNB", 18, 0, &[Some(2300)], chain, 100)
171}
172
173fn wrapped_native_pol(chain: Chain, address: &str) -> Token {
174    Token::new(&Bytes::from_str(address).unwrap(), "WMATIC", 18, 0, &[Some(2300)], chain, 100)
175}
176
177impl Chain {
178    pub fn id(&self) -> u64 {
179        match self {
180            Chain::Ethereum => 1,
181            Chain::ZkSync => 324,
182            Chain::Arbitrum => 42161,
183            Chain::Starknet => 0,
184            Chain::Base => 8453,
185            Chain::Bsc => 56,
186            Chain::Unichain => 130,
187            Chain::Polygon => 137,
188        }
189    }
190
191    /// Returns a default TVL threshold in native token units for the given tier.
192    ///
193    /// Values are approximate and target a USD-equivalent range, not a precise conversion.
194    /// Native token prices used: ETH ~$2,000, POL ~$0.10, BNB ~$630.
195    /// These prices are volatile, and used as a reference. They should not be updated often,
196    /// unless big price movements occour, making an update necessary.
197    pub fn default_tvl_threshold(&self, tier: TvlThresholdTier) -> f64 {
198        match (self, tier) {
199            // ETH-native chains: 10 ETH ≈ $20K, 100 ETH ≈ $200K.
200            // Starknet uses ETH-denominated TVL in Tycho (STRK tracked separately).
201            (
202                Chain::Ethereum |
203                Chain::Starknet |
204                Chain::ZkSync |
205                Chain::Arbitrum |
206                Chain::Base |
207                Chain::Unichain,
208                TvlThresholdTier::Low,
209            ) => 10.0,
210            (
211                Chain::Ethereum |
212                Chain::Starknet |
213                Chain::ZkSync |
214                Chain::Arbitrum |
215                Chain::Base |
216                Chain::Unichain,
217                TvlThresholdTier::Medium,
218            ) => 100.0,
219
220            // Polygon (POL ≈ $0.10): 200_000 POL ≈ $20K, 2_000_000 POL ≈ $200K
221            (Chain::Polygon, TvlThresholdTier::Low) => 200_000.0,
222            (Chain::Polygon, TvlThresholdTier::Medium) => 2_000_000.0,
223
224            // BSC (BNB ≈ $630): 32 BNB ≈ $20K, 320 BNB ≈ $200K
225            (Chain::Bsc, TvlThresholdTier::Low) => 32.0,
226            (Chain::Bsc, TvlThresholdTier::Medium) => 320.0,
227        }
228    }
229
230    /// Returns the native token for the chain.
231    pub fn native_token(&self) -> Token {
232        match self {
233            Chain::Ethereum => native_eth(Chain::Ethereum),
234            // It was decided that STRK token will be tracked as a dedicated AccountBalance on
235            // Starknet accounts and ETH balances will be tracked as a native balance.
236            Chain::Starknet => native_eth(Chain::Starknet),
237            Chain::ZkSync => native_eth(Chain::ZkSync),
238            Chain::Arbitrum => native_eth(Chain::Arbitrum),
239            Chain::Base => native_eth(Chain::Base),
240            Chain::Bsc => native_bsc(Chain::Bsc),
241            Chain::Unichain => native_eth(Chain::Unichain),
242            Chain::Polygon => native_pol(Chain::Polygon),
243        }
244    }
245
246    /// Returns the wrapped native token for the chain.
247    pub fn wrapped_native_token(&self) -> Token {
248        match self {
249            Chain::Ethereum => {
250                wrapped_native_eth(Chain::Ethereum, "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
251            }
252            // Starknet does not have a wrapped native token
253            Chain::Starknet => {
254                wrapped_native_eth(Chain::Starknet, "0x0000000000000000000000000000000000000000")
255            }
256            Chain::ZkSync => {
257                wrapped_native_eth(Chain::ZkSync, "0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91")
258            }
259            Chain::Arbitrum => {
260                wrapped_native_eth(Chain::Arbitrum, "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1")
261            }
262            Chain::Base => {
263                wrapped_native_eth(Chain::Base, "0x4200000000000000000000000000000000000006")
264            }
265            Chain::Bsc => {
266                wrapped_native_bsc(Chain::Bsc, "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c")
267            }
268            Chain::Unichain => {
269                wrapped_native_eth(Chain::Unichain, "0x4200000000000000000000000000000000000006")
270            }
271            Chain::Polygon => {
272                wrapped_native_pol(Chain::Polygon, "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270")
273            }
274        }
275    }
276}
277
278#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Default)]
279pub struct ExtractorIdentity {
280    pub chain: Chain,
281    pub name: String,
282}
283
284impl ExtractorIdentity {
285    pub fn new(chain: Chain, name: &str) -> Self {
286        Self { chain, name: name.to_owned() }
287    }
288}
289
290impl std::fmt::Display for ExtractorIdentity {
291    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292        write!(f, "{}:{}", self.chain, self.name)
293    }
294}
295
296impl From<ExtractorIdentity> for dto::ExtractorIdentity {
297    fn from(value: ExtractorIdentity) -> Self {
298        dto::ExtractorIdentity { chain: value.chain.into(), name: value.name }
299    }
300}
301
302impl From<dto::ExtractorIdentity> for ExtractorIdentity {
303    fn from(value: dto::ExtractorIdentity) -> Self {
304        Self { chain: value.chain.into(), name: value.name }
305    }
306}
307
308#[derive(Debug, PartialEq, Clone)]
309pub struct ExtractionState {
310    pub name: String,
311    pub chain: Chain,
312    pub attributes: serde_json::Value,
313    pub cursor: Vec<u8>,
314    pub block_hash: Bytes,
315}
316
317impl ExtractionState {
318    pub fn new(
319        name: String,
320        chain: Chain,
321        attributes: Option<serde_json::Value>,
322        cursor: &[u8],
323        block_hash: Bytes,
324    ) -> Self {
325        ExtractionState {
326            name,
327            chain,
328            attributes: attributes.unwrap_or_default(),
329            cursor: cursor.to_vec(),
330            block_hash,
331        }
332    }
333}
334
335#[derive(PartialEq, Debug, Clone, Default, Deserialize, Serialize)]
336pub enum ImplementationType {
337    #[default]
338    Vm,
339    Custom,
340}
341
342#[derive(PartialEq, Debug, Clone, Default, Deserialize, Serialize)]
343pub enum FinancialType {
344    #[default]
345    Swap,
346    Psm,
347    Debt,
348    Leverage,
349}
350
351#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
352pub struct ProtocolType {
353    pub name: String,
354    pub financial_type: FinancialType,
355    pub attribute_schema: Option<serde_json::Value>,
356    pub implementation: ImplementationType,
357}
358
359impl ProtocolType {
360    pub fn new(
361        name: String,
362        financial_type: FinancialType,
363        attribute_schema: Option<serde_json::Value>,
364        implementation: ImplementationType,
365    ) -> Self {
366        ProtocolType { name, financial_type, attribute_schema, implementation }
367    }
368}
369
370#[derive(Debug, PartialEq, Eq, Default, Copy, Clone, Deserialize, Serialize, DeepSizeOf)]
371pub enum ChangeType {
372    #[default]
373    Update,
374    Deletion,
375    Creation,
376}
377
378#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
379pub struct ContractId {
380    pub address: Address,
381    pub chain: Chain,
382}
383
384/// Uniquely identifies a contract on a specific chain.
385impl ContractId {
386    pub fn new(chain: Chain, address: Address) -> Self {
387        Self { address, chain }
388    }
389
390    pub fn address(&self) -> &Address {
391        &self.address
392    }
393}
394
395impl Display for ContractId {
396    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397        write!(f, "{:?}: 0x{}", self.chain, hex::encode(&self.address))
398    }
399}
400
401#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
402pub struct PaginationParams {
403    pub page: i64,
404    pub page_size: i64,
405}
406
407impl PaginationParams {
408    pub fn new(page: i64, page_size: i64) -> Self {
409        Self { page, page_size }
410    }
411
412    pub fn offset(&self) -> i64 {
413        self.page * self.page_size
414    }
415}
416
417impl From<&dto::PaginationParams> for PaginationParams {
418    fn from(value: &dto::PaginationParams) -> Self {
419        PaginationParams { page: value.page, page_size: value.page_size }
420    }
421}
422
423#[derive(Error, Debug, PartialEq)]
424pub enum MergeError {
425    #[error("Can't merge {0} from differring idendities: Expected {1}, got {2}")]
426    IdMismatch(String, String, String),
427    #[error("Can't merge {0} from different blocks: 0x{1:x} != 0x{2:x}")]
428    BlockMismatch(String, Bytes, Bytes),
429    #[error("Can't merge {0} from the same transaction: 0x{1:x}")]
430    SameTransaction(String, Bytes),
431    #[error("Can't merge {0} with lower transaction index: {1} > {2}")]
432    TransactionOrderError(String, u64, u64),
433    #[error("Cannot merge: {0}")]
434    InvalidState(String),
435}