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 Custom(CustomChainId),
99}
100
101impl DeepSizeOf for Chain {
102 fn deep_size_of_children(&self, _context: &mut deepsize::Context) -> usize {
103 0
104 }
105}
106
107impl Chain {
108 pub fn builtin_from_str(s: &str) -> Option<Self> {
110 match s {
111 "ethereum" => Some(Chain::Ethereum),
112 "starknet" => Some(Chain::Starknet),
113 "zksync" => Some(Chain::ZkSync),
114 "arbitrum" => Some(Chain::Arbitrum),
115 "base" => Some(Chain::Base),
116 "bsc" => Some(Chain::Bsc),
117 "unichain" => Some(Chain::Unichain),
118 "polygon" => Some(Chain::Polygon),
119 "plasma" => Some(Chain::Plasma),
120 _ => None,
121 }
122 }
123
124 pub fn custom(name: &str) -> Result<Self, ChainConfigError> {
128 CustomChainId::checked(name, chain_registry()).map(Chain::Custom)
129 }
130}
131
132impl FromStr for Chain {
133 type Err = ChainConfigError;
134
135 fn from_str(s: &str) -> Result<Self, Self::Err> {
139 if let Some(chain) = Self::builtin_from_str(s) {
140 return Ok(chain);
141 }
142 Self::custom(s)
143 }
144}
145
146impl Display for Chain {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 match self {
149 Chain::Ethereum => f.write_str("ethereum"),
150 Chain::Starknet => f.write_str("starknet"),
151 Chain::ZkSync => f.write_str("zksync"),
152 Chain::Arbitrum => f.write_str("arbitrum"),
153 Chain::Base => f.write_str("base"),
154 Chain::Bsc => f.write_str("bsc"),
155 Chain::Unichain => f.write_str("unichain"),
156 Chain::Polygon => f.write_str("polygon"),
157 Chain::Plasma => f.write_str("plasma"),
158 Chain::Custom(name) => f.write_str(name.as_str()),
159 }
160 }
161}
162
163impl From<dto::Chain> for Chain {
164 fn from(value: dto::Chain) -> Self {
165 match value {
166 dto::Chain::Ethereum => Chain::Ethereum,
167 dto::Chain::Starknet => Chain::Starknet,
168 dto::Chain::ZkSync => Chain::ZkSync,
169 dto::Chain::Arbitrum => Chain::Arbitrum,
170 dto::Chain::Base => Chain::Base,
171 dto::Chain::Bsc => Chain::Bsc,
172 dto::Chain::Unichain => Chain::Unichain,
173 dto::Chain::Polygon => Chain::Polygon,
174 dto::Chain::Plasma => Chain::Plasma,
175 dto::Chain::Custom(name) => Chain::custom(name.as_str()).unwrap_or_else(|e| {
176 panic!(
177 "received custom chain '{name}' with no registered config: {e}; install it via \
178 the chain config file (TYCHO_CHAINS_CONFIG, default ./chains.yaml) or \
179 init_chain_registry before decoding wire data"
180 )
181 }),
182 }
183 }
184}
185
186impl From<dto::ChangeType> for ChangeType {
187 fn from(value: dto::ChangeType) -> Self {
188 match value {
189 dto::ChangeType::Update => ChangeType::Update,
190 dto::ChangeType::Creation => ChangeType::Creation,
191 dto::ChangeType::Deletion => ChangeType::Deletion,
192 dto::ChangeType::Unspecified => ChangeType::Update,
193 }
194 }
195}
196
197fn native_eth(chain: Chain) -> Token {
198 Token::new(
199 &Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
200 "ETH",
201 18,
202 0,
203 &[Some(2300)],
204 chain,
205 100,
206 )
207}
208
209fn native_bsc(chain: Chain) -> Token {
210 Token::new(
211 &Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
212 "BNB",
213 18,
214 0,
215 &[Some(2300)],
216 chain,
217 100,
218 )
219}
220
221fn wrapped_native_eth(chain: Chain, address: &str) -> Token {
222 Token::new(&Bytes::from_str(address).unwrap(), "WETH", 18, 0, &[Some(2300)], chain, 100)
223}
224
225fn native_pol(chain: Chain) -> Token {
226 Token::new(
227 &Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
228 "POL",
229 18,
230 0,
231 &[Some(2300)],
232 chain,
233 100,
234 )
235}
236
237fn native_xpl(chain: Chain) -> Token {
238 Token::new(
239 &Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
240 "XPL",
241 18,
242 0,
243 &[Some(2300)],
244 chain,
245 100,
246 )
247}
248
249fn try_resolve_custom<'a>(
252 id: &CustomChainId,
253 registry: &'a ChainConfigRegistry,
254) -> Result<&'a CustomChainConfig, ChainConfigError> {
255 registry
256 .get(id.as_str())
257 .ok_or_else(|| ChainConfigError::UnknownChain(id.as_str().to_owned()))
258}
259
260fn expect_registered<T>(result: Result<T, ChainConfigError>) -> T {
264 result.unwrap_or_else(|e| {
265 panic!(
266 "internal invariant violation resolving custom chain config: {e}; Chain::Custom is \
267 validated against the set-once chain registry at construction"
268 )
269 })
270}
271
272fn native_custom(chain: Chain, cfg: &CustomChainConfig) -> Token {
273 let addr = Bytes::from(cfg.native.address.as_bytes().to_vec());
274 Token::new(
275 &addr,
276 cfg.native.symbol.as_str(),
277 cfg.native.decimals as u32,
278 0,
279 &[Some(2300)],
280 chain,
281 100,
282 )
283}
284
285fn wrapped_native_bsc(chain: Chain, address: &str) -> Token {
286 Token::new(&Bytes::from_str(address).unwrap(), "WBNB", 18, 0, &[Some(2300)], chain, 100)
287}
288
289fn wrapped_native_pol(chain: Chain, address: &str) -> Token {
290 Token::new(&Bytes::from_str(address).unwrap(), "WMATIC", 18, 0, &[Some(2300)], chain, 100)
291}
292
293fn wrapped_native_xpl(chain: Chain, address: &str) -> Token {
294 Token::new(&Bytes::from_str(address).unwrap(), "WXPL", 18, 0, &[Some(2300)], chain, 100)
295}
296
297fn wrapped_native_custom(chain: Chain, cfg: &CustomChainConfig) -> Token {
298 let addr = Bytes::from(
299 cfg.wrapped_native
300 .address
301 .as_bytes()
302 .to_vec(),
303 );
304 Token::new(
305 &addr,
306 cfg.wrapped_native.symbol.as_str(),
307 cfg.wrapped_native.decimals as u32,
308 0,
309 &[Some(2300)],
310 chain,
311 100,
312 )
313}
314
315impl Chain {
316 pub fn id(&self) -> u64 {
320 expect_registered(self.try_id())
321 }
322
323 pub fn try_id(&self) -> Result<u64, ChainConfigError> {
326 Ok(match self {
327 Chain::Ethereum => 1,
328 Chain::ZkSync => 324,
329 Chain::Arbitrum => 42161,
330 Chain::Starknet => 0,
331 Chain::Base => 8453,
332 Chain::Bsc => 56,
333 Chain::Unichain => 130,
334 Chain::Polygon => 137,
335 Chain::Plasma => 9745,
336 Chain::Custom(id) => try_resolve_custom(id, chain_registry())?.chain_id,
337 })
338 }
339
340 pub fn default_tvl_threshold(&self, tier: TvlThresholdTier) -> f64 {
350 expect_registered(self.try_default_tvl_threshold(tier))
351 }
352
353 pub fn try_default_tvl_threshold(
356 &self,
357 tier: TvlThresholdTier,
358 ) -> Result<f64, ChainConfigError> {
359 Ok(match (self, tier) {
360 (
363 Chain::Ethereum |
364 Chain::Starknet |
365 Chain::ZkSync |
366 Chain::Arbitrum |
367 Chain::Base |
368 Chain::Unichain,
369 TvlThresholdTier::Low,
370 ) => 10.0,
371 (
372 Chain::Ethereum |
373 Chain::Starknet |
374 Chain::ZkSync |
375 Chain::Arbitrum |
376 Chain::Base |
377 Chain::Unichain,
378 TvlThresholdTier::Medium,
379 ) => 100.0,
380
381 (Chain::Polygon, TvlThresholdTier::Low) => 200_000.0,
383 (Chain::Polygon, TvlThresholdTier::Medium) => 2_000_000.0,
384
385 (Chain::Plasma, TvlThresholdTier::Low) => 200_000.0,
387 (Chain::Plasma, TvlThresholdTier::Medium) => 2_000_000.0,
388
389 (Chain::Bsc, TvlThresholdTier::Low) => 32.0,
391 (Chain::Bsc, TvlThresholdTier::Medium) => 320.0,
392
393 (Chain::Custom(id), TvlThresholdTier::Low) => {
394 try_resolve_custom(id, chain_registry())?
395 .default_tvl_thresholds
396 .low
397 }
398 (Chain::Custom(id), TvlThresholdTier::Medium) => {
399 try_resolve_custom(id, chain_registry())?
400 .default_tvl_thresholds
401 .medium
402 }
403 })
404 }
405
406 pub fn native_token(&self) -> Token {
409 expect_registered(self.try_native_token())
410 }
411
412 pub fn try_native_token(&self) -> Result<Token, ChainConfigError> {
415 Ok(match self {
416 Chain::Ethereum => native_eth(Chain::Ethereum),
417 Chain::Starknet => native_eth(Chain::Starknet),
420 Chain::ZkSync => native_eth(Chain::ZkSync),
421 Chain::Arbitrum => native_eth(Chain::Arbitrum),
422 Chain::Base => native_eth(Chain::Base),
423 Chain::Bsc => native_bsc(Chain::Bsc),
424 Chain::Unichain => native_eth(Chain::Unichain),
425 Chain::Polygon => native_pol(Chain::Polygon),
426 Chain::Plasma => native_xpl(Chain::Plasma),
427 Chain::Custom(id) => native_custom(*self, try_resolve_custom(id, chain_registry())?),
428 })
429 }
430
431 pub fn wrapped_native_token(&self) -> Token {
434 expect_registered(self.try_wrapped_native_token())
435 }
436
437 pub fn try_wrapped_native_token(&self) -> Result<Token, ChainConfigError> {
440 Ok(match self {
441 Chain::Ethereum => {
442 wrapped_native_eth(Chain::Ethereum, "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
443 }
444 Chain::Starknet => {
446 wrapped_native_eth(Chain::Starknet, "0x0000000000000000000000000000000000000000")
447 }
448 Chain::ZkSync => {
449 wrapped_native_eth(Chain::ZkSync, "0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91")
450 }
451 Chain::Arbitrum => {
452 wrapped_native_eth(Chain::Arbitrum, "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1")
453 }
454 Chain::Base => {
455 wrapped_native_eth(Chain::Base, "0x4200000000000000000000000000000000000006")
456 }
457 Chain::Bsc => {
458 wrapped_native_bsc(Chain::Bsc, "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c")
459 }
460 Chain::Unichain => {
461 wrapped_native_eth(Chain::Unichain, "0x4200000000000000000000000000000000000006")
462 }
463 Chain::Polygon => {
464 wrapped_native_pol(Chain::Polygon, "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270")
465 }
466 Chain::Plasma => {
467 wrapped_native_xpl(Chain::Plasma, "0x6100E367285b01F48D07953803A2d8dCA5D19873")
468 }
469 Chain::Custom(id) => {
470 wrapped_native_custom(*self, try_resolve_custom(id, chain_registry())?)
471 }
472 })
473 }
474
475 pub fn block_time_secs(&self) -> u64 {
478 expect_registered(self.try_block_time_secs())
479 }
480
481 pub fn try_block_time_secs(&self) -> Result<u64, ChainConfigError> {
484 Ok(match self {
485 Chain::Ethereum => 12,
486 Chain::Starknet => 2,
487 Chain::ZkSync => 3,
488 Chain::Arbitrum => 1,
489 Chain::Base => 2,
490 Chain::Bsc => 1,
491 Chain::Unichain => 1,
492 Chain::Polygon => 2,
493 Chain::Plasma => 1,
494 Chain::Custom(id) => try_resolve_custom(id, chain_registry())?.block_time_secs,
495 })
496 }
497}
498
499#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Default)]
500pub struct ExtractorIdentity {
501 pub chain: Chain,
502 pub name: String,
503}
504
505impl ExtractorIdentity {
506 pub fn new(chain: Chain, name: &str) -> Self {
507 Self { chain, name: name.to_owned() }
508 }
509}
510
511impl std::fmt::Display for ExtractorIdentity {
512 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
513 write!(f, "{}:{}", self.chain, self.name)
514 }
515}
516
517impl From<ExtractorIdentity> for dto::ExtractorIdentity {
518 fn from(value: ExtractorIdentity) -> Self {
519 dto::ExtractorIdentity { chain: value.chain.into(), name: value.name }
520 }
521}
522
523impl From<dto::ExtractorIdentity> for ExtractorIdentity {
524 fn from(value: dto::ExtractorIdentity) -> Self {
525 Self { chain: value.chain.into(), name: value.name }
526 }
527}
528
529#[derive(Debug, PartialEq, Clone)]
530pub struct ExtractionState {
531 pub name: String,
532 pub chain: Chain,
533 pub attributes: serde_json::Value,
534 pub cursor: Vec<u8>,
535 pub block_hash: Bytes,
536}
537
538impl ExtractionState {
539 pub fn new(
540 name: String,
541 chain: Chain,
542 attributes: Option<serde_json::Value>,
543 cursor: &[u8],
544 block_hash: Bytes,
545 ) -> Self {
546 ExtractionState {
547 name,
548 chain,
549 attributes: attributes.unwrap_or_default(),
550 cursor: cursor.to_vec(),
551 block_hash,
552 }
553 }
554}
555
556#[derive(PartialEq, Debug, Clone, Default, Deserialize, Serialize)]
557pub enum ImplementationType {
558 #[default]
559 Vm,
560 Custom,
561}
562
563#[derive(PartialEq, Debug, Clone, Default, Deserialize, Serialize)]
564pub enum FinancialType {
565 #[default]
566 Swap,
567 Psm,
568 Debt,
569 Leverage,
570}
571
572#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
573pub struct ProtocolType {
574 pub name: String,
575 pub financial_type: FinancialType,
576 pub attribute_schema: Option<serde_json::Value>,
577 pub implementation: ImplementationType,
578}
579
580impl ProtocolType {
581 pub fn new(
582 name: String,
583 financial_type: FinancialType,
584 attribute_schema: Option<serde_json::Value>,
585 implementation: ImplementationType,
586 ) -> Self {
587 ProtocolType { name, financial_type, attribute_schema, implementation }
588 }
589}
590
591#[derive(Debug, PartialEq, Eq, Default, Copy, Clone, Deserialize, Serialize, DeepSizeOf)]
592pub enum ChangeType {
593 #[default]
594 Update,
595 Deletion,
596 Creation,
597}
598
599#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
600pub struct ContractId {
601 pub address: Address,
602 pub chain: Chain,
603}
604
605impl ContractId {
607 pub fn new(chain: Chain, address: Address) -> Self {
608 Self { address, chain }
609 }
610
611 pub fn address(&self) -> &Address {
612 &self.address
613 }
614}
615
616impl Display for ContractId {
617 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
618 write!(f, "{:?}: 0x{}", self.chain, hex::encode(&self.address))
619 }
620}
621
622#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
623pub struct PaginationParams {
624 pub page: i64,
625 pub page_size: i64,
626}
627
628impl PaginationParams {
629 pub fn new(page: i64, page_size: i64) -> Self {
630 Self { page, page_size }
631 }
632
633 pub fn offset(&self) -> i64 {
634 self.page * self.page_size
635 }
636}
637
638impl From<&dto::PaginationParams> for PaginationParams {
639 fn from(value: &dto::PaginationParams) -> Self {
640 PaginationParams { page: value.page, page_size: value.page_size }
641 }
642}
643
644#[derive(Error, Debug, PartialEq)]
645pub enum MergeError {
646 #[error("Can't merge {0} from differring idendities: Expected {1}, got {2}")]
647 IdMismatch(String, String, String),
648 #[error("Can't merge {0} from different blocks: 0x{1:x} != 0x{2:x}")]
649 BlockMismatch(String, Bytes, Bytes),
650 #[error("Can't merge {0} from the same transaction: 0x{1:x}")]
651 SameTransaction(String, Bytes),
652 #[error("Can't merge {0} with lower transaction index: {1} > {2}")]
653 TransactionOrderError(String, u64, u64),
654 #[error("Cannot merge: {0}")]
655 InvalidState(String),
656}
657
658#[cfg(test)]
662mod tests {
663 use arrayvec::ArrayString;
664
665 use super::{
666 chain_config::{
667 init_chain_registry, ChainAddress, ChainConfigError, ChainTokenConfig, TvlThresholds,
668 },
669 *,
670 };
671
672 fn test_config() -> CustomChainConfig {
673 CustomChainConfig::try_new(
674 "testchain",
675 9999,
676 5,
677 ChainTokenConfig::try_new("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "TST", 18)
678 .unwrap(),
679 ChainTokenConfig::try_new("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "WTST", 18)
680 .unwrap(),
681 TvlThresholds::new(50.0, 500.0),
682 )
683 .unwrap()
684 }
685
686 fn init_test_registry() {
687 init_chain_registry(ChainConfigRegistry::from_configs([test_config()]).unwrap())
688 .expect("chain registry already initialised; run tests under nextest");
689 }
690
691 #[test]
692 fn test_custom_chain_display() {
693 init_test_registry();
694 assert_eq!(
695 Chain::custom("testchain")
696 .unwrap()
697 .to_string(),
698 "testchain"
699 );
700 }
701
702 #[test]
703 fn test_from_str_custom_returns_err() {
704 assert!("custom".parse::<Chain>().is_err());
705 assert!("unknown".parse::<Chain>().is_err());
706 }
707
708 #[test]
709 fn test_custom_unregistered_returns_err() {
710 init_test_registry();
711 assert_eq!(Chain::custom("nope"), Err(ChainConfigError::UnknownChain("nope".to_owned())));
712 }
713
714 #[test]
715 fn test_from_dto_registered_custom_roundtrips() {
716 init_test_registry();
717 let dto_chain: dto::Chain = Chain::custom("testchain")
718 .unwrap()
719 .into();
720 let chain: Chain = dto_chain.into();
721 assert_eq!(chain.id(), 9999);
722 }
723
724 #[test]
725 #[should_panic(expected = "no registered config")]
726 fn test_from_dto_unregistered_custom_panics() {
727 let dto_chain = dto::Chain::Custom(ArrayString::from("nope").unwrap());
728 let _: Chain = dto_chain.into();
729 }
730
731 #[test]
732 fn test_try_accessors_ok_for_registered_custom() {
733 init_test_registry();
734 let chain = Chain::custom("testchain").unwrap();
735 assert_eq!(chain.try_id().unwrap(), 9999);
736 assert_eq!(chain.try_block_time_secs().unwrap(), 5);
737 assert_eq!(
738 chain
739 .try_default_tvl_threshold(TvlThresholdTier::Low)
740 .unwrap(),
741 50.0
742 );
743 assert_eq!(chain.try_native_token().unwrap().symbol, "TST");
744 assert_eq!(
745 chain
746 .try_wrapped_native_token()
747 .unwrap()
748 .symbol,
749 "WTST"
750 );
751 }
752
753 #[test]
754 fn test_try_accessors_err_for_unregistered_custom() {
755 let ghost: Chain = serde_json::from_str(r#"{"custom":"ghostchain"}"#).unwrap();
758 assert_eq!(ghost.try_id(), Err(ChainConfigError::UnknownChain("ghostchain".to_owned())));
759 assert!(ghost.try_native_token().is_err());
760 }
761
762 #[test]
763 fn test_chain_stays_small() {
764 assert!(
767 std::mem::size_of::<Chain>() <= 40,
768 "Chain is {} bytes",
769 std::mem::size_of::<Chain>()
770 );
771 }
772
773 #[test]
774 fn test_custom_chain_id() {
775 init_test_registry();
776 let chain = Chain::custom("testchain").unwrap();
777 assert_eq!(chain.id(), 9999);
778 }
779
780 #[test]
781 fn test_custom_chain_tvl_thresholds() {
782 init_test_registry();
783 let chain = Chain::custom("testchain").unwrap();
784 assert_eq!(chain.default_tvl_threshold(TvlThresholdTier::Low), 50.0);
785 assert_eq!(chain.default_tvl_threshold(TvlThresholdTier::Medium), 500.0);
786 }
787
788 #[test]
789 fn test_custom_chain_native_token() {
790 init_test_registry();
791 let chain = Chain::custom("testchain").unwrap();
792 let token = chain.native_token();
793 assert_eq!(token.symbol, "TST");
794 assert_eq!(token.decimals, 18);
795 assert_eq!(token.chain, chain);
796 assert_eq!(token.address, Bytes::from(vec![0xAA; 20]));
797 }
798
799 #[test]
800 fn test_custom_chain_wrapped_native_token() {
801 init_test_registry();
802 let chain = Chain::custom("testchain").unwrap();
803 let token = chain.wrapped_native_token();
804 assert_eq!(token.symbol, "WTST");
805 assert_eq!(token.chain, chain);
806 assert_eq!(token.address, Bytes::from(vec![0xBB; 20]));
807 }
808
809 #[test]
810 fn test_chain_address_new_rejects_oversized_input() {
811 assert_eq!(ChainAddress::new(&[0u8; 33]), Err(ChainConfigError::AddressTooLong(33)));
812 }
813
814 #[test]
815 fn test_chain_address_as_bytes_returns_active_slice() {
816 let addr = ChainAddress::new(&[0xAA; 20]).unwrap();
817 assert_eq!(addr.as_bytes(), &[0xAA; 20]);
818 assert_eq!(addr.as_bytes().len(), 20);
819 }
820}