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