1pub mod blockchain;
2pub mod chain_config;
3pub mod contract;
4pub mod error;
5pub mod protocol;
6pub mod token;
7
8use std::{collections::HashMap, fmt::Display, str::FromStr};
9
10pub use blockchain::{BlockChanges, TxWithContractChanges};
11use chain_config::{
12 chain_registry, ChainConfigError, ChainConfigRegistry, CustomChainConfig, CustomChainId,
13 TvlThresholdTier,
14};
15use deepsize::DeepSizeOf;
16use serde::{Deserialize, Serialize};
17use thiserror::Error;
18use token::Token;
19
20use crate::{dto, Bytes};
21
22pub type Address = Bytes;
25
26pub type BlockHash = Bytes;
29
30pub type TxHash = Bytes;
33
34pub type Code = Bytes;
36
37pub type CodeHash = Bytes;
39
40pub type Balance = Bytes;
42
43pub type StoreKey = Bytes;
45
46pub type AttrStoreKey = String;
48
49pub type StoreVal = Bytes;
51
52pub type ContractStore = HashMap<StoreKey, StoreVal>;
54pub type ContractStoreDeltas = HashMap<StoreKey, Option<StoreVal>>;
55pub type AccountToContractStoreDeltas = HashMap<Address, ContractStoreDeltas>;
56
57pub type ComponentId = String;
59
60pub type ProtocolSystem = String;
62
63pub type EntryPointId = String;
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
84#[serde(rename_all = "lowercase")]
85#[non_exhaustive]
86pub enum Chain {
87 #[default]
88 Ethereum,
89 Starknet,
90 ZkSync,
91 Arbitrum,
92 Base,
93 Bsc,
94 Unichain,
95 Polygon,
96 Plasma,
97 Robinhood,
98 Custom(CustomChainId),
100}
101
102impl DeepSizeOf for Chain {
103 fn deep_size_of_children(&self, _context: &mut deepsize::Context) -> usize {
104 0
105 }
106}
107
108impl Chain {
109 pub fn builtin_from_str(s: &str) -> Option<Self> {
111 match s {
112 "ethereum" => Some(Chain::Ethereum),
113 "starknet" => Some(Chain::Starknet),
114 "zksync" => Some(Chain::ZkSync),
115 "arbitrum" => Some(Chain::Arbitrum),
116 "base" => Some(Chain::Base),
117 "bsc" => Some(Chain::Bsc),
118 "unichain" => Some(Chain::Unichain),
119 "polygon" => Some(Chain::Polygon),
120 "plasma" => Some(Chain::Plasma),
121 "robinhood" => Some(Chain::Robinhood),
122 _ => None,
123 }
124 }
125
126 pub fn custom(name: &str) -> Result<Self, ChainConfigError> {
130 CustomChainId::checked(name, chain_registry()).map(Chain::Custom)
131 }
132}
133
134impl FromStr for Chain {
135 type Err = ChainConfigError;
136
137 fn from_str(s: &str) -> Result<Self, Self::Err> {
141 if let Some(chain) = Self::builtin_from_str(s) {
142 return Ok(chain);
143 }
144 Self::custom(s)
145 }
146}
147
148impl Display for Chain {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 match self {
151 Chain::Ethereum => f.write_str("ethereum"),
152 Chain::Starknet => f.write_str("starknet"),
153 Chain::ZkSync => f.write_str("zksync"),
154 Chain::Arbitrum => f.write_str("arbitrum"),
155 Chain::Base => f.write_str("base"),
156 Chain::Bsc => f.write_str("bsc"),
157 Chain::Unichain => f.write_str("unichain"),
158 Chain::Polygon => f.write_str("polygon"),
159 Chain::Plasma => f.write_str("plasma"),
160 Chain::Robinhood => f.write_str("robinhood"),
161 Chain::Custom(name) => f.write_str(name.as_str()),
162 }
163 }
164}
165
166impl From<dto::Chain> for Chain {
167 fn from(value: dto::Chain) -> Self {
168 match value {
169 dto::Chain::Ethereum => Chain::Ethereum,
170 dto::Chain::Starknet => Chain::Starknet,
171 dto::Chain::ZkSync => Chain::ZkSync,
172 dto::Chain::Arbitrum => Chain::Arbitrum,
173 dto::Chain::Base => Chain::Base,
174 dto::Chain::Bsc => Chain::Bsc,
175 dto::Chain::Unichain => Chain::Unichain,
176 dto::Chain::Polygon => Chain::Polygon,
177 dto::Chain::Plasma => Chain::Plasma,
178 dto::Chain::Robinhood => Chain::Robinhood,
179 dto::Chain::Custom(name) => Chain::custom(name.as_str()).unwrap_or_else(|e| {
180 panic!(
181 "received custom chain '{name}' with no registered config: {e}; install it via \
182 the chain config file (TYCHO_CHAINS_CONFIG, default ./chains.yaml) or \
183 init_chain_registry before decoding wire data"
184 )
185 }),
186 }
187 }
188}
189
190impl From<dto::ChangeType> for ChangeType {
191 fn from(value: dto::ChangeType) -> Self {
192 match value {
193 dto::ChangeType::Update => ChangeType::Update,
194 dto::ChangeType::Creation => ChangeType::Creation,
195 dto::ChangeType::Deletion => ChangeType::Deletion,
196 dto::ChangeType::Unspecified => ChangeType::Update,
197 }
198 }
199}
200
201fn native_eth(chain: Chain) -> Token {
202 Token::new(
203 &Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
204 "ETH",
205 18,
206 0,
207 &[Some(2300)],
208 chain,
209 100,
210 )
211}
212
213fn native_bsc(chain: Chain) -> Token {
214 Token::new(
215 &Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
216 "BNB",
217 18,
218 0,
219 &[Some(2300)],
220 chain,
221 100,
222 )
223}
224
225fn wrapped_native_eth(chain: Chain, address: &str) -> Token {
226 Token::new(&Bytes::from_str(address).unwrap(), "WETH", 18, 0, &[Some(2300)], chain, 100)
227}
228
229fn native_pol(chain: Chain) -> Token {
230 Token::new(
231 &Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
232 "POL",
233 18,
234 0,
235 &[Some(2300)],
236 chain,
237 100,
238 )
239}
240
241fn native_xpl(chain: Chain) -> Token {
242 Token::new(
243 &Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
244 "XPL",
245 18,
246 0,
247 &[Some(2300)],
248 chain,
249 100,
250 )
251}
252
253fn try_resolve_custom<'a>(
256 id: &CustomChainId,
257 registry: &'a ChainConfigRegistry,
258) -> Result<&'a CustomChainConfig, ChainConfigError> {
259 registry
260 .get(id.as_str())
261 .ok_or_else(|| ChainConfigError::UnknownChain(id.as_str().to_owned()))
262}
263
264fn expect_registered<T>(result: Result<T, ChainConfigError>) -> T {
268 result.unwrap_or_else(|e| {
269 panic!(
270 "internal invariant violation resolving custom chain config: {e}; Chain::Custom is \
271 validated against the set-once chain registry at construction"
272 )
273 })
274}
275
276fn native_custom(chain: Chain, cfg: &CustomChainConfig) -> Token {
277 let addr = Bytes::from(cfg.native.address.as_bytes().to_vec());
278 Token::new(
279 &addr,
280 cfg.native.symbol.as_str(),
281 cfg.native.decimals as u32,
282 0,
283 &[Some(2300)],
284 chain,
285 100,
286 )
287}
288
289fn wrapped_native_bsc(chain: Chain, address: &str) -> Token {
290 Token::new(&Bytes::from_str(address).unwrap(), "WBNB", 18, 0, &[Some(2300)], chain, 100)
291}
292
293fn wrapped_native_pol(chain: Chain, address: &str) -> Token {
294 Token::new(&Bytes::from_str(address).unwrap(), "WMATIC", 18, 0, &[Some(2300)], chain, 100)
295}
296
297fn wrapped_native_xpl(chain: Chain, address: &str) -> Token {
298 Token::new(&Bytes::from_str(address).unwrap(), "WXPL", 18, 0, &[Some(2300)], chain, 100)
299}
300
301fn wrapped_native_custom(chain: Chain, cfg: &CustomChainConfig) -> Token {
302 let addr = Bytes::from(
303 cfg.wrapped_native
304 .address
305 .as_bytes()
306 .to_vec(),
307 );
308 Token::new(
309 &addr,
310 cfg.wrapped_native.symbol.as_str(),
311 cfg.wrapped_native.decimals as u32,
312 0,
313 &[Some(2300)],
314 chain,
315 100,
316 )
317}
318
319impl Chain {
320 pub fn id(&self) -> u64 {
324 expect_registered(self.try_id())
325 }
326
327 pub fn try_id(&self) -> Result<u64, ChainConfigError> {
330 Ok(match self {
331 Chain::Ethereum => 1,
332 Chain::ZkSync => 324,
333 Chain::Arbitrum => 42161,
334 Chain::Starknet => 0,
335 Chain::Base => 8453,
336 Chain::Bsc => 56,
337 Chain::Unichain => 130,
338 Chain::Polygon => 137,
339 Chain::Plasma => 9745,
340 Chain::Robinhood => 4663,
341 Chain::Custom(id) => try_resolve_custom(id, chain_registry())?.chain_id,
342 })
343 }
344
345 pub fn default_tvl_threshold(&self, tier: TvlThresholdTier) -> f64 {
355 expect_registered(self.try_default_tvl_threshold(tier))
356 }
357
358 pub fn try_default_tvl_threshold(
361 &self,
362 tier: TvlThresholdTier,
363 ) -> Result<f64, ChainConfigError> {
364 Ok(match (self, tier) {
365 (
368 Chain::Ethereum |
369 Chain::Starknet |
370 Chain::ZkSync |
371 Chain::Arbitrum |
372 Chain::Base |
373 Chain::Unichain |
374 Chain::Robinhood,
375 TvlThresholdTier::Low,
376 ) => 10.0,
377 (
378 Chain::Ethereum |
379 Chain::Starknet |
380 Chain::ZkSync |
381 Chain::Arbitrum |
382 Chain::Base |
383 Chain::Unichain |
384 Chain::Robinhood,
385 TvlThresholdTier::Medium,
386 ) => 100.0,
387
388 (Chain::Polygon, TvlThresholdTier::Low) => 200_000.0,
390 (Chain::Polygon, TvlThresholdTier::Medium) => 2_000_000.0,
391
392 (Chain::Plasma, TvlThresholdTier::Low) => 200_000.0,
394 (Chain::Plasma, TvlThresholdTier::Medium) => 2_000_000.0,
395
396 (Chain::Bsc, TvlThresholdTier::Low) => 32.0,
398 (Chain::Bsc, TvlThresholdTier::Medium) => 320.0,
399
400 (Chain::Custom(id), TvlThresholdTier::Low) => {
401 try_resolve_custom(id, chain_registry())?
402 .default_tvl_thresholds
403 .low
404 }
405 (Chain::Custom(id), TvlThresholdTier::Medium) => {
406 try_resolve_custom(id, chain_registry())?
407 .default_tvl_thresholds
408 .medium
409 }
410 })
411 }
412
413 pub fn native_token(&self) -> Token {
416 expect_registered(self.try_native_token())
417 }
418
419 pub fn try_native_token(&self) -> Result<Token, ChainConfigError> {
422 Ok(match self {
423 Chain::Ethereum => native_eth(Chain::Ethereum),
424 Chain::Starknet => native_eth(Chain::Starknet),
427 Chain::ZkSync => native_eth(Chain::ZkSync),
428 Chain::Arbitrum => native_eth(Chain::Arbitrum),
429 Chain::Base => native_eth(Chain::Base),
430 Chain::Bsc => native_bsc(Chain::Bsc),
431 Chain::Unichain => native_eth(Chain::Unichain),
432 Chain::Polygon => native_pol(Chain::Polygon),
433 Chain::Plasma => native_xpl(Chain::Plasma),
434 Chain::Robinhood => native_eth(Chain::Robinhood),
435 Chain::Custom(id) => native_custom(*self, try_resolve_custom(id, chain_registry())?),
436 })
437 }
438
439 pub fn wrapped_native_token(&self) -> Token {
442 expect_registered(self.try_wrapped_native_token())
443 }
444
445 pub fn try_wrapped_native_token(&self) -> Result<Token, ChainConfigError> {
448 Ok(match self {
449 Chain::Ethereum => {
450 wrapped_native_eth(Chain::Ethereum, "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
451 }
452 Chain::Starknet => {
454 wrapped_native_eth(Chain::Starknet, "0x0000000000000000000000000000000000000000")
455 }
456 Chain::ZkSync => {
457 wrapped_native_eth(Chain::ZkSync, "0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91")
458 }
459 Chain::Arbitrum => {
460 wrapped_native_eth(Chain::Arbitrum, "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1")
461 }
462 Chain::Base => {
463 wrapped_native_eth(Chain::Base, "0x4200000000000000000000000000000000000006")
464 }
465 Chain::Bsc => {
466 wrapped_native_bsc(Chain::Bsc, "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c")
467 }
468 Chain::Unichain => {
469 wrapped_native_eth(Chain::Unichain, "0x4200000000000000000000000000000000000006")
470 }
471 Chain::Polygon => {
472 wrapped_native_pol(Chain::Polygon, "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270")
473 }
474 Chain::Plasma => {
475 wrapped_native_xpl(Chain::Plasma, "0x6100E367285b01F48D07953803A2d8dCA5D19873")
476 }
477 Chain::Robinhood => {
478 wrapped_native_eth(Chain::Robinhood, "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73")
479 }
480 Chain::Custom(id) => {
481 wrapped_native_custom(*self, try_resolve_custom(id, chain_registry())?)
482 }
483 })
484 }
485
486 pub fn block_time_secs(&self) -> u64 {
489 expect_registered(self.try_block_time_secs())
490 }
491
492 pub fn try_block_time_secs(&self) -> Result<u64, ChainConfigError> {
495 Ok(match self {
496 Chain::Ethereum => 12,
497 Chain::Starknet => 2,
498 Chain::ZkSync => 3,
499 Chain::Arbitrum => 1,
500 Chain::Base => 2,
501 Chain::Bsc => 1,
502 Chain::Unichain => 1,
503 Chain::Polygon => 2,
504 Chain::Plasma => 1,
505 Chain::Robinhood => 1,
506 Chain::Custom(id) => try_resolve_custom(id, chain_registry())?.block_time_secs,
507 })
508 }
509}
510
511#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Default)]
512pub struct ExtractorIdentity {
513 pub chain: Chain,
514 pub name: String,
515}
516
517impl ExtractorIdentity {
518 pub fn new(chain: Chain, name: &str) -> Self {
519 Self { chain, name: name.to_owned() }
520 }
521}
522
523impl std::fmt::Display for ExtractorIdentity {
524 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
525 write!(f, "{}:{}", self.chain, self.name)
526 }
527}
528
529impl From<ExtractorIdentity> for dto::ExtractorIdentity {
530 fn from(value: ExtractorIdentity) -> Self {
531 dto::ExtractorIdentity { chain: value.chain.into(), name: value.name }
532 }
533}
534
535impl From<dto::ExtractorIdentity> for ExtractorIdentity {
536 fn from(value: dto::ExtractorIdentity) -> Self {
537 Self { chain: value.chain.into(), name: value.name }
538 }
539}
540
541#[derive(Debug, PartialEq, Clone)]
542pub struct ExtractionState {
543 pub name: String,
544 pub chain: Chain,
545 pub attributes: serde_json::Value,
546 pub cursor: Vec<u8>,
547 pub block_hash: Bytes,
548}
549
550impl ExtractionState {
551 pub fn new(
552 name: String,
553 chain: Chain,
554 attributes: Option<serde_json::Value>,
555 cursor: &[u8],
556 block_hash: Bytes,
557 ) -> Self {
558 ExtractionState {
559 name,
560 chain,
561 attributes: attributes.unwrap_or_default(),
562 cursor: cursor.to_vec(),
563 block_hash,
564 }
565 }
566}
567
568#[derive(PartialEq, Debug, Clone, Default, Deserialize, Serialize)]
569pub enum ImplementationType {
570 #[default]
571 Vm,
572 Custom,
573}
574
575#[derive(PartialEq, Debug, Clone, Default, Deserialize, Serialize)]
576pub enum FinancialType {
577 #[default]
578 Swap,
579 Psm,
580 Debt,
581 Leverage,
582}
583
584#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
585pub struct ProtocolType {
586 pub name: String,
587 pub financial_type: FinancialType,
588 pub attribute_schema: Option<serde_json::Value>,
589 pub implementation: ImplementationType,
590}
591
592impl ProtocolType {
593 pub fn new(
594 name: String,
595 financial_type: FinancialType,
596 attribute_schema: Option<serde_json::Value>,
597 implementation: ImplementationType,
598 ) -> Self {
599 ProtocolType { name, financial_type, attribute_schema, implementation }
600 }
601}
602
603#[derive(Debug, PartialEq, Eq, Default, Copy, Clone, Deserialize, Serialize, DeepSizeOf)]
604pub enum ChangeType {
605 #[default]
606 Update,
607 Deletion,
608 Creation,
609}
610
611#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
612pub struct ContractId {
613 pub address: Address,
614 pub chain: Chain,
615}
616
617impl ContractId {
619 pub fn new(chain: Chain, address: Address) -> Self {
620 Self { address, chain }
621 }
622
623 pub fn address(&self) -> &Address {
624 &self.address
625 }
626}
627
628impl Display for ContractId {
629 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
630 write!(f, "{:?}: 0x{}", self.chain, hex::encode(&self.address))
631 }
632}
633
634#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
635pub struct PaginationParams {
636 pub page: i64,
637 pub page_size: i64,
638}
639
640impl PaginationParams {
641 pub fn new(page: i64, page_size: i64) -> Self {
642 Self { page, page_size }
643 }
644
645 pub fn offset(&self) -> i64 {
646 self.page * self.page_size
647 }
648}
649
650impl From<&dto::PaginationParams> for PaginationParams {
651 fn from(value: &dto::PaginationParams) -> Self {
652 PaginationParams { page: value.page, page_size: value.page_size }
653 }
654}
655
656#[derive(Error, Debug, PartialEq)]
657pub enum MergeError {
658 #[error("Can't merge {0} from differring idendities: Expected {1}, got {2}")]
659 IdMismatch(String, String, String),
660 #[error("Can't merge {0} from different blocks: 0x{1:x} != 0x{2:x}")]
661 BlockMismatch(String, Bytes, Bytes),
662 #[error("Can't merge {0} from the same transaction: 0x{1:x}")]
663 SameTransaction(String, Bytes),
664 #[error("Can't merge {0} with lower transaction index: {1} > {2}")]
665 TransactionOrderError(String, u64, u64),
666 #[error("Cannot merge: {0}")]
667 InvalidState(String),
668}
669
670#[cfg(test)]
674mod tests {
675 use arrayvec::ArrayString;
676
677 use super::{
678 chain_config::{
679 init_chain_registry, ChainAddress, ChainConfigError, ChainTokenConfig, TvlThresholds,
680 },
681 *,
682 };
683
684 fn test_config() -> CustomChainConfig {
685 CustomChainConfig::try_new(
686 "testchain",
687 9999,
688 5,
689 ChainTokenConfig::try_new("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "TST", 18)
690 .unwrap(),
691 ChainTokenConfig::try_new("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "WTST", 18)
692 .unwrap(),
693 TvlThresholds::new(50.0, 500.0),
694 )
695 .unwrap()
696 }
697
698 fn init_test_registry() {
699 init_chain_registry(ChainConfigRegistry::from_configs([test_config()]).unwrap())
700 .expect("chain registry already initialised; run tests under nextest");
701 }
702
703 #[test]
704 fn test_custom_chain_display() {
705 init_test_registry();
706 assert_eq!(
707 Chain::custom("testchain")
708 .unwrap()
709 .to_string(),
710 "testchain"
711 );
712 }
713
714 #[test]
715 fn test_from_str_custom_returns_err() {
716 assert!("custom".parse::<Chain>().is_err());
717 assert!("unknown".parse::<Chain>().is_err());
718 }
719
720 #[test]
721 fn test_custom_unregistered_returns_err() {
722 init_test_registry();
723 assert_eq!(Chain::custom("nope"), Err(ChainConfigError::UnknownChain("nope".to_owned())));
724 }
725
726 #[test]
727 fn test_from_dto_registered_custom_roundtrips() {
728 init_test_registry();
729 let dto_chain: dto::Chain = Chain::custom("testchain")
730 .unwrap()
731 .into();
732 let chain: Chain = dto_chain.into();
733 assert_eq!(chain.id(), 9999);
734 }
735
736 #[test]
737 #[should_panic(expected = "no registered config")]
738 fn test_from_dto_unregistered_custom_panics() {
739 let dto_chain = dto::Chain::Custom(ArrayString::from("nope").unwrap());
740 let _: Chain = dto_chain.into();
741 }
742
743 #[test]
744 fn test_try_accessors_ok_for_registered_custom() {
745 init_test_registry();
746 let chain = Chain::custom("testchain").unwrap();
747 assert_eq!(chain.try_id().unwrap(), 9999);
748 assert_eq!(chain.try_block_time_secs().unwrap(), 5);
749 assert_eq!(
750 chain
751 .try_default_tvl_threshold(TvlThresholdTier::Low)
752 .unwrap(),
753 50.0
754 );
755 assert_eq!(chain.try_native_token().unwrap().symbol, "TST");
756 assert_eq!(
757 chain
758 .try_wrapped_native_token()
759 .unwrap()
760 .symbol,
761 "WTST"
762 );
763 }
764
765 #[test]
766 fn test_try_accessors_err_for_unregistered_custom() {
767 let ghost: Chain = serde_json::from_str(r#"{"custom":"ghostchain"}"#).unwrap();
770 assert_eq!(ghost.try_id(), Err(ChainConfigError::UnknownChain("ghostchain".to_owned())));
771 assert!(ghost.try_native_token().is_err());
772 }
773
774 #[test]
775 fn test_chain_stays_small() {
776 assert!(
779 std::mem::size_of::<Chain>() <= 40,
780 "Chain is {} bytes",
781 std::mem::size_of::<Chain>()
782 );
783 }
784
785 #[test]
786 fn test_custom_chain_id() {
787 init_test_registry();
788 let chain = Chain::custom("testchain").unwrap();
789 assert_eq!(chain.id(), 9999);
790 }
791
792 #[test]
793 fn test_custom_chain_tvl_thresholds() {
794 init_test_registry();
795 let chain = Chain::custom("testchain").unwrap();
796 assert_eq!(chain.default_tvl_threshold(TvlThresholdTier::Low), 50.0);
797 assert_eq!(chain.default_tvl_threshold(TvlThresholdTier::Medium), 500.0);
798 }
799
800 #[test]
801 fn test_custom_chain_native_token() {
802 init_test_registry();
803 let chain = Chain::custom("testchain").unwrap();
804 let token = chain.native_token();
805 assert_eq!(token.symbol, "TST");
806 assert_eq!(token.decimals, 18);
807 assert_eq!(token.chain, chain);
808 assert_eq!(token.address, Bytes::from(vec![0xAA; 20]));
809 }
810
811 #[test]
812 fn test_custom_chain_wrapped_native_token() {
813 init_test_registry();
814 let chain = Chain::custom("testchain").unwrap();
815 let token = chain.wrapped_native_token();
816 assert_eq!(token.symbol, "WTST");
817 assert_eq!(token.chain, chain);
818 assert_eq!(token.address, Bytes::from(vec![0xBB; 20]));
819 }
820
821 #[test]
822 fn test_chain_address_new_rejects_oversized_input() {
823 assert_eq!(ChainAddress::new(&[0u8; 33]), Err(ChainConfigError::AddressTooLong(33)));
824 }
825
826 #[test]
827 fn test_robinhood_chain_id() {
828 assert_eq!(Chain::Robinhood.id(), 4663);
829 }
830
831 #[test]
832 fn test_robinhood_chain_display() {
833 assert_eq!(Chain::Robinhood.to_string(), "robinhood");
834 }
835
836 #[test]
837 fn test_robinhood_chain_from_str() {
838 assert_eq!("robinhood".parse::<Chain>().unwrap(), Chain::Robinhood);
839 }
840
841 #[test]
842 fn test_robinhood_native_token() {
843 let token = Chain::Robinhood.native_token();
844 assert_eq!(token.symbol, "ETH");
845 assert_eq!(token.chain, Chain::Robinhood);
846 assert_eq!(
847 token.address,
848 Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap()
849 );
850 }
851
852 #[test]
853 fn test_robinhood_wrapped_native_token() {
854 let token = Chain::Robinhood.wrapped_native_token();
855 assert_eq!(token.symbol, "WETH");
856 assert_eq!(token.chain, Chain::Robinhood);
857 assert_eq!(
858 token.address,
859 Bytes::from_str("0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73").unwrap()
860 );
861 }
862
863 #[test]
864 fn test_robinhood_default_tvl_threshold() {
865 assert_eq!(Chain::Robinhood.default_tvl_threshold(TvlThresholdTier::Low), 10.0);
866 assert_eq!(Chain::Robinhood.default_tvl_threshold(TvlThresholdTier::Medium), 100.0);
867 }
868
869 #[test]
870 fn test_robinhood_block_time_secs() {
871 assert_eq!(Chain::Robinhood.block_time_secs(), 1);
872 }
873
874 #[test]
875 fn test_chain_address_as_bytes_returns_active_slice() {
876 let addr = ChainAddress::new(&[0xAA; 20]).unwrap();
877 assert_eq!(addr.as_bytes(), &[0xAA; 20]);
878 assert_eq!(addr.as_bytes().len(), 20);
879 }
880}