1#![allow(deprecated)]
8use std::{
9 collections::{BTreeMap, HashMap, HashSet},
10 fmt,
11 hash::{Hash, Hasher},
12};
13
14use chrono::{NaiveDateTime, Utc};
15use deepsize::{Context, DeepSizeOf};
16use serde::{de, Deserialize, Deserializer, Serialize};
17use strum_macros::{Display, EnumString};
18use thiserror::Error;
19use utoipa::{IntoParams, ToSchema};
20use uuid::Uuid;
21
22use crate::{
23 models::{self, Address, Balance, Code, ComponentId, StoreKey, StoreVal},
24 serde_primitives::{
25 hex_bytes, hex_bytes_option, hex_hashmap_key, hex_hashmap_key_value, hex_hashmap_value,
26 },
27 Bytes,
28};
29
30#[derive(
32 Debug,
33 Clone,
34 Copy,
35 PartialEq,
36 Eq,
37 Hash,
38 Serialize,
39 Deserialize,
40 EnumString,
41 Display,
42 Default,
43 ToSchema,
44 DeepSizeOf,
45)]
46#[serde(rename_all = "lowercase")]
47#[strum(serialize_all = "lowercase")]
48pub enum Chain {
49 #[default]
50 Ethereum,
51 Starknet,
52 ZkSync,
53 Arbitrum,
54 Base,
55 Bsc,
56 Unichain,
57 Polygon,
58}
59
60pub use models::TvlThresholdTier;
61
62impl Chain {
63 pub fn default_tvl_threshold(&self, tier: TvlThresholdTier) -> f64 {
65 models::Chain::from(*self).default_tvl_threshold(tier)
66 }
67}
68
69impl From<models::contract::Account> for ResponseAccount {
70 fn from(value: models::contract::Account) -> Self {
71 ResponseAccount::new(
72 value.chain.into(),
73 value.address,
74 value.title,
75 value.slots,
76 value.native_balance,
77 value
78 .token_balances
79 .into_iter()
80 .map(|(k, v)| (k, v.balance))
81 .collect(),
82 value.code,
83 value.code_hash,
84 value.balance_modify_tx,
85 value.code_modify_tx,
86 value.creation_tx,
87 )
88 }
89}
90
91impl From<models::Chain> for Chain {
92 fn from(value: models::Chain) -> Self {
93 match value {
94 models::Chain::Ethereum => Chain::Ethereum,
95 models::Chain::Starknet => Chain::Starknet,
96 models::Chain::ZkSync => Chain::ZkSync,
97 models::Chain::Arbitrum => Chain::Arbitrum,
98 models::Chain::Base => Chain::Base,
99 models::Chain::Bsc => Chain::Bsc,
100 models::Chain::Unichain => Chain::Unichain,
101 models::Chain::Polygon => Chain::Polygon,
102 }
103 }
104}
105
106#[derive(
107 Debug,
108 PartialEq,
109 Default,
110 Copy,
111 Clone,
112 Deserialize,
113 Serialize,
114 ToSchema,
115 EnumString,
116 Display,
117 DeepSizeOf,
118)]
119pub enum ChangeType {
120 #[default]
121 Update,
122 Deletion,
123 Creation,
124 Unspecified,
125}
126
127impl From<models::ChangeType> for ChangeType {
128 fn from(value: models::ChangeType) -> Self {
129 match value {
130 models::ChangeType::Update => ChangeType::Update,
131 models::ChangeType::Creation => ChangeType::Creation,
132 models::ChangeType::Deletion => ChangeType::Deletion,
133 }
134 }
135}
136
137impl ChangeType {
138 pub fn merge(&self, other: &Self) -> Self {
139 if matches!(self, Self::Creation) {
140 Self::Creation
141 } else {
142 *other
143 }
144 }
145}
146
147#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Default)]
148pub struct ExtractorIdentity {
149 pub chain: Chain,
150 pub name: String,
151}
152
153impl ExtractorIdentity {
154 pub fn new(chain: Chain, name: &str) -> Self {
155 Self { chain, name: name.to_owned() }
156 }
157}
158
159impl fmt::Display for ExtractorIdentity {
160 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161 write!(f, "{}:{}", self.chain, self.name)
162 }
163}
164
165#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
167#[serde(tag = "method", rename_all = "lowercase")]
168pub enum Command {
169 Subscribe {
170 extractor_id: ExtractorIdentity,
171 include_state: bool,
172 #[serde(default)]
175 compression: bool,
176 #[serde(default)]
179 partial_blocks: bool,
180 },
181 Unsubscribe {
182 subscription_id: Uuid,
183 },
184}
185
186#[derive(Error, Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
194pub enum WebsocketError {
195 #[error("Extractor not found: {0}")]
196 ExtractorNotFound(ExtractorIdentity),
197
198 #[error("Subscription not found: {0}")]
199 SubscriptionNotFound(Uuid),
200
201 #[error("Failed to parse JSON: {1}, msg: {0}")]
202 ParseError(String, String),
203
204 #[error("Failed to subscribe to extractor: {0}")]
205 SubscribeError(ExtractorIdentity),
206
207 #[error("Failed to compress message for subscription: {0}, error: {1}")]
208 CompressionError(Uuid, String),
209}
210
211impl From<crate::models::error::WebsocketError> for WebsocketError {
212 fn from(value: crate::models::error::WebsocketError) -> Self {
213 match value {
214 crate::models::error::WebsocketError::ExtractorNotFound(eid) => {
215 Self::ExtractorNotFound(eid.into())
216 }
217 crate::models::error::WebsocketError::SubscriptionNotFound(sid) => {
218 Self::SubscriptionNotFound(sid)
219 }
220 crate::models::error::WebsocketError::ParseError(raw, error) => {
221 Self::ParseError(error.to_string(), raw)
222 }
223 crate::models::error::WebsocketError::SubscribeError(eid) => {
224 Self::SubscribeError(eid.into())
225 }
226 crate::models::error::WebsocketError::CompressionError(sid, error) => {
227 Self::CompressionError(sid, error.to_string())
228 }
229 }
230 }
231}
232
233#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
235#[serde(tag = "method", rename_all = "lowercase")]
236pub enum Response {
237 NewSubscription { extractor_id: ExtractorIdentity, subscription_id: Uuid },
238 SubscriptionEnded { subscription_id: Uuid },
239 Error(WebsocketError),
240}
241
242#[allow(clippy::large_enum_variant)]
244#[derive(Serialize, Deserialize, Debug, Display, Clone)]
245#[serde(untagged)]
246pub enum WebSocketMessage {
247 BlockAggregatedChanges { subscription_id: Uuid, deltas: BlockAggregatedChanges },
248 Response(Response),
249}
250
251#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default, ToSchema)]
252pub struct Block {
253 pub number: u64,
254 #[serde(with = "hex_bytes")]
255 pub hash: Bytes,
256 #[serde(with = "hex_bytes")]
257 pub parent_hash: Bytes,
258 pub chain: Chain,
259 pub ts: NaiveDateTime,
260}
261
262#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema, Eq, Hash, DeepSizeOf)]
263#[serde(deny_unknown_fields)]
264pub struct BlockParam {
265 #[schema(value_type=Option<String>)]
266 #[serde(with = "hex_bytes_option", default)]
267 pub hash: Option<Bytes>,
268 #[deprecated(
269 note = "The `chain` field is deprecated and will be removed in a future version."
270 )]
271 #[serde(default)]
272 pub chain: Option<Chain>,
273 #[serde(default)]
274 pub number: Option<i64>,
275}
276
277impl From<&Block> for BlockParam {
278 fn from(value: &Block) -> Self {
279 BlockParam { hash: Some(value.hash.clone()), chain: None, number: None }
281 }
282}
283
284#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
285pub struct TokenBalances(#[serde(with = "hex_hashmap_key")] pub HashMap<Bytes, ComponentBalance>);
286
287impl From<HashMap<Bytes, ComponentBalance>> for TokenBalances {
288 fn from(value: HashMap<Bytes, ComponentBalance>) -> Self {
289 TokenBalances(value)
290 }
291}
292
293#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
294pub struct Transaction {
295 #[serde(with = "hex_bytes")]
296 pub hash: Bytes,
297 #[serde(with = "hex_bytes")]
298 pub block_hash: Bytes,
299 #[serde(with = "hex_bytes")]
300 pub from: Bytes,
301 #[serde(with = "hex_bytes_option")]
302 pub to: Option<Bytes>,
303 pub index: u64,
304}
305
306impl Transaction {
307 pub fn new(hash: Bytes, block_hash: Bytes, from: Bytes, to: Option<Bytes>, index: u64) -> Self {
308 Self { hash, block_hash, from, to, index }
309 }
310}
311
312#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
314pub struct BlockAggregatedChanges {
315 pub extractor: String,
316 pub chain: Chain,
317 pub block: Block,
318 pub finalized_block_height: u64,
319 pub revert: bool,
320 #[serde(with = "hex_hashmap_key", default)]
321 pub new_tokens: HashMap<Bytes, ResponseToken>,
322 #[serde(alias = "account_deltas", with = "hex_hashmap_key")]
323 pub account_updates: HashMap<Bytes, AccountUpdate>,
324 #[serde(alias = "state_deltas")]
325 pub state_updates: HashMap<String, ProtocolStateDelta>,
326 pub new_protocol_components: HashMap<String, ProtocolComponent>,
327 pub deleted_protocol_components: HashMap<String, ProtocolComponent>,
328 pub component_balances: HashMap<String, TokenBalances>,
329 pub account_balances: HashMap<Bytes, HashMap<Bytes, AccountBalance>>,
330 pub component_tvl: HashMap<String, f64>,
331 pub dci_update: DCIUpdate,
332 #[serde(default, skip_serializing_if = "Option::is_none")]
334 pub partial_block_index: Option<u32>,
335}
336
337impl BlockAggregatedChanges {
338 #[allow(clippy::too_many_arguments)]
339 pub fn new(
340 extractor: &str,
341 chain: Chain,
342 block: Block,
343 finalized_block_height: u64,
344 revert: bool,
345 account_updates: HashMap<Bytes, AccountUpdate>,
346 state_updates: HashMap<String, ProtocolStateDelta>,
347 new_protocol_components: HashMap<String, ProtocolComponent>,
348 deleted_protocol_components: HashMap<String, ProtocolComponent>,
349 component_balances: HashMap<String, HashMap<Bytes, ComponentBalance>>,
350 account_balances: HashMap<Bytes, HashMap<Bytes, AccountBalance>>,
351 dci_update: DCIUpdate,
352 ) -> Self {
353 BlockAggregatedChanges {
354 extractor: extractor.to_owned(),
355 chain,
356 block,
357 finalized_block_height,
358 revert,
359 new_tokens: HashMap::new(),
360 account_updates,
361 state_updates,
362 new_protocol_components,
363 deleted_protocol_components,
364 component_balances: component_balances
365 .into_iter()
366 .map(|(k, v)| (k, v.into()))
367 .collect(),
368 account_balances,
369 component_tvl: HashMap::new(),
370 dci_update,
371 partial_block_index: None,
372 }
373 }
374
375 pub fn merge(mut self, other: Self) -> Self {
376 other
377 .account_updates
378 .into_iter()
379 .for_each(|(k, v)| {
380 self.account_updates
381 .entry(k)
382 .and_modify(|e| {
383 e.merge(&v);
384 })
385 .or_insert(v);
386 });
387
388 other
389 .state_updates
390 .into_iter()
391 .for_each(|(k, v)| {
392 self.state_updates
393 .entry(k)
394 .and_modify(|e| {
395 e.merge(&v);
396 })
397 .or_insert(v);
398 });
399
400 other
401 .component_balances
402 .into_iter()
403 .for_each(|(k, v)| {
404 self.component_balances
405 .entry(k)
406 .and_modify(|e| e.0.extend(v.0.clone()))
407 .or_insert_with(|| v);
408 });
409
410 other
411 .account_balances
412 .into_iter()
413 .for_each(|(k, v)| {
414 self.account_balances
415 .entry(k)
416 .and_modify(|e| e.extend(v.clone()))
417 .or_insert(v);
418 });
419
420 self.component_tvl
421 .extend(other.component_tvl);
422 self.new_protocol_components
423 .extend(other.new_protocol_components);
424 self.deleted_protocol_components
425 .extend(other.deleted_protocol_components);
426 self.revert = other.revert;
427 self.block = other.block;
428
429 self
430 }
431
432 pub fn get_block(&self) -> &Block {
433 &self.block
434 }
435
436 pub fn is_revert(&self) -> bool {
437 self.revert
438 }
439
440 pub fn filter_by_component<F: Fn(&str) -> bool>(&mut self, keep: F) {
441 self.state_updates
442 .retain(|k, _| keep(k));
443 self.component_balances
444 .retain(|k, _| keep(k));
445 self.component_tvl
446 .retain(|k, _| keep(k));
447 }
448
449 pub fn filter_by_contract<F: Fn(&Bytes) -> bool>(&mut self, keep: F) {
450 self.account_updates
451 .retain(|k, _| keep(k));
452 self.account_balances
453 .retain(|k, _| keep(k));
454 }
455
456 pub fn n_changes(&self) -> usize {
457 self.account_updates.len() + self.state_updates.len()
458 }
459
460 pub fn drop_state(&self) -> Self {
461 Self {
462 extractor: self.extractor.clone(),
463 chain: self.chain,
464 block: self.block.clone(),
465 finalized_block_height: self.finalized_block_height,
466 revert: self.revert,
467 new_tokens: self.new_tokens.clone(),
468 account_updates: HashMap::new(),
469 state_updates: HashMap::new(),
470 new_protocol_components: self.new_protocol_components.clone(),
471 deleted_protocol_components: self.deleted_protocol_components.clone(),
472 component_balances: self.component_balances.clone(),
473 account_balances: self.account_balances.clone(),
474 component_tvl: self.component_tvl.clone(),
475 dci_update: self.dci_update.clone(),
476 partial_block_index: self.partial_block_index,
477 }
478 }
479
480 pub fn is_partial_block(&self) -> bool {
481 self.partial_block_index.is_some()
482 }
483}
484
485impl From<models::blockchain::Block> for Block {
486 fn from(value: models::blockchain::Block) -> Self {
487 Self {
488 number: value.number,
489 hash: value.hash,
490 parent_hash: value.parent_hash,
491 chain: value.chain.into(),
492 ts: value.ts,
493 }
494 }
495}
496
497impl From<models::protocol::ComponentBalance> for ComponentBalance {
498 fn from(value: models::protocol::ComponentBalance) -> Self {
499 Self {
500 token: value.token,
501 balance: value.balance,
502 balance_float: value.balance_float,
503 modify_tx: value.modify_tx,
504 component_id: value.component_id,
505 }
506 }
507}
508
509impl From<models::contract::AccountBalance> for AccountBalance {
510 fn from(value: models::contract::AccountBalance) -> Self {
511 Self {
512 account: value.account,
513 token: value.token,
514 balance: value.balance,
515 modify_tx: value.modify_tx,
516 }
517 }
518}
519
520impl From<models::blockchain::BlockAggregatedChanges> for BlockAggregatedChanges {
521 fn from(value: models::blockchain::BlockAggregatedChanges) -> Self {
522 Self {
523 extractor: value.extractor,
524 chain: value.chain.into(),
525 block: value.block.into(),
526 finalized_block_height: value.finalized_block_height,
527 revert: value.revert,
528 account_updates: value
529 .account_deltas
530 .into_iter()
531 .map(|(k, v)| (k, v.into()))
532 .collect(),
533 state_updates: value
534 .state_deltas
535 .into_iter()
536 .map(|(k, v)| (k, v.into()))
537 .collect(),
538 new_protocol_components: value
539 .new_protocol_components
540 .into_iter()
541 .map(|(k, v)| (k, v.into()))
542 .collect(),
543 deleted_protocol_components: value
544 .deleted_protocol_components
545 .into_iter()
546 .map(|(k, v)| (k, v.into()))
547 .collect(),
548 component_balances: value
549 .component_balances
550 .into_iter()
551 .map(|(component_id, v)| {
552 let balances: HashMap<Bytes, ComponentBalance> = v
553 .into_iter()
554 .map(|(k, v)| (k, ComponentBalance::from(v)))
555 .collect();
556 (component_id, balances.into())
557 })
558 .collect(),
559 account_balances: value
560 .account_balances
561 .into_iter()
562 .map(|(k, v)| {
563 (
564 k,
565 v.into_iter()
566 .map(|(k, v)| (k, v.into()))
567 .collect(),
568 )
569 })
570 .collect(),
571 dci_update: value.dci_update.into(),
572 new_tokens: value
573 .new_tokens
574 .into_iter()
575 .map(|(k, v)| (k, v.into()))
576 .collect(),
577 component_tvl: value.component_tvl,
578 partial_block_index: value.partial_block_index,
579 }
580 }
581}
582
583#[derive(PartialEq, Serialize, Deserialize, Clone, Debug, ToSchema)]
584pub struct AccountUpdate {
585 #[serde(with = "hex_bytes")]
586 #[schema(value_type=String)]
587 pub address: Bytes,
588 pub chain: Chain,
589 #[serde(with = "hex_hashmap_key_value")]
590 #[schema(value_type=HashMap<String, String>)]
591 pub slots: HashMap<Bytes, Bytes>,
592 #[serde(with = "hex_bytes_option")]
593 #[schema(value_type=Option<String>)]
594 pub balance: Option<Bytes>,
595 #[serde(with = "hex_bytes_option")]
596 #[schema(value_type=Option<String>)]
597 pub code: Option<Bytes>,
598 pub change: ChangeType,
599}
600
601impl AccountUpdate {
602 pub fn new(
603 address: Bytes,
604 chain: Chain,
605 slots: HashMap<Bytes, Bytes>,
606 balance: Option<Bytes>,
607 code: Option<Bytes>,
608 change: ChangeType,
609 ) -> Self {
610 Self { address, chain, slots, balance, code, change }
611 }
612
613 pub fn merge(&mut self, other: &Self) {
614 self.slots.extend(
615 other
616 .slots
617 .iter()
618 .map(|(k, v)| (k.clone(), v.clone())),
619 );
620 self.balance.clone_from(&other.balance);
621 self.code.clone_from(&other.code);
622 self.change = self.change.merge(&other.change);
623 }
624}
625
626impl From<models::contract::AccountDelta> for AccountUpdate {
627 fn from(value: models::contract::AccountDelta) -> Self {
628 let code = value.code().clone();
629 let change_type = value.change_type().into();
630 AccountUpdate::new(
631 value.address,
632 value.chain.into(),
633 value
634 .slots
635 .into_iter()
636 .map(|(k, v)| (k, v.unwrap_or_default()))
637 .collect(),
638 value.balance,
639 code,
640 change_type,
641 )
642 }
643}
644
645#[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize, ToSchema)]
647pub struct ProtocolComponent {
648 pub id: String,
650 pub protocol_system: String,
652 pub protocol_type_name: String,
654 pub chain: Chain,
655 #[schema(value_type=Vec<String>)]
657 pub tokens: Vec<Bytes>,
658 #[serde(alias = "contract_addresses")]
661 #[schema(value_type=Vec<String>)]
662 pub contract_ids: Vec<Bytes>,
663 #[serde(with = "hex_hashmap_value")]
665 #[schema(value_type=HashMap<String, String>)]
666 pub static_attributes: HashMap<String, Bytes>,
667 #[serde(default)]
669 pub change: ChangeType,
670 #[serde(with = "hex_bytes")]
672 #[schema(value_type=String)]
673 pub creation_tx: Bytes,
674 pub created_at: NaiveDateTime,
676}
677
678impl DeepSizeOf for ProtocolComponent {
680 fn deep_size_of_children(&self, ctx: &mut Context) -> usize {
681 self.id.deep_size_of_children(ctx) +
682 self.protocol_system
683 .deep_size_of_children(ctx) +
684 self.protocol_type_name
685 .deep_size_of_children(ctx) +
686 self.chain.deep_size_of_children(ctx) +
687 self.tokens.deep_size_of_children(ctx) +
688 self.contract_ids
689 .deep_size_of_children(ctx) +
690 self.static_attributes
691 .deep_size_of_children(ctx) +
692 self.change.deep_size_of_children(ctx) +
693 self.creation_tx
694 .deep_size_of_children(ctx)
695 }
696}
697
698impl<T> From<models::protocol::ProtocolComponent<T>> for ProtocolComponent
699where
700 T: Into<Address> + Clone,
701{
702 fn from(value: models::protocol::ProtocolComponent<T>) -> Self {
703 Self {
704 id: value.id,
705 protocol_system: value.protocol_system,
706 protocol_type_name: value.protocol_type_name,
707 chain: value.chain.into(),
708 tokens: value
709 .tokens
710 .into_iter()
711 .map(|t| t.into())
712 .collect(),
713 contract_ids: value.contract_addresses,
714 static_attributes: value.static_attributes,
715 change: value.change.into(),
716 creation_tx: value.creation_tx,
717 created_at: value.created_at,
718 }
719 }
720}
721
722#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
723pub struct ComponentBalance {
724 #[serde(with = "hex_bytes")]
725 pub token: Bytes,
726 pub balance: Bytes,
727 pub balance_float: f64,
728 #[serde(with = "hex_bytes")]
729 pub modify_tx: Bytes,
730 pub component_id: String,
731}
732
733#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, ToSchema)]
734pub struct ProtocolStateDelta {
736 pub component_id: String,
737 #[schema(value_type=HashMap<String, String>)]
738 pub updated_attributes: HashMap<String, Bytes>,
739 pub deleted_attributes: HashSet<String>,
740}
741
742impl From<models::protocol::ProtocolComponentStateDelta> for ProtocolStateDelta {
743 fn from(value: models::protocol::ProtocolComponentStateDelta) -> Self {
744 Self {
745 component_id: value.component_id,
746 updated_attributes: value.updated_attributes,
747 deleted_attributes: value.deleted_attributes,
748 }
749 }
750}
751
752impl ProtocolStateDelta {
753 pub fn merge(&mut self, other: &Self) {
772 self.updated_attributes
774 .retain(|k, _| !other.deleted_attributes.contains(k));
775
776 self.deleted_attributes.retain(|attr| {
778 !other
779 .updated_attributes
780 .contains_key(attr)
781 });
782
783 self.updated_attributes.extend(
785 other
786 .updated_attributes
787 .iter()
788 .map(|(k, v)| (k.clone(), v.clone())),
789 );
790
791 self.deleted_attributes
793 .extend(other.deleted_attributes.iter().cloned());
794 }
795}
796
797#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, Eq, Hash, DeepSizeOf)]
799#[serde(deny_unknown_fields)]
800pub struct PaginationParams {
801 #[serde(default)]
803 pub page: i64,
804 #[serde(default)]
806 #[schema(default = 100)]
807 pub page_size: i64,
808}
809
810impl PaginationParams {
811 pub fn new(page: i64, page_size: i64) -> Self {
812 Self { page, page_size }
813 }
814}
815
816impl Default for PaginationParams {
817 fn default() -> Self {
818 PaginationParams { page: 0, page_size: 100 }
819 }
820}
821
822pub trait PaginationLimits {
827 const MAX_PAGE_SIZE_COMPRESSED: i64;
829
830 const MAX_PAGE_SIZE_UNCOMPRESSED: i64;
832
833 fn effective_max_page_size(compression: bool) -> i64 {
835 if compression {
836 Self::MAX_PAGE_SIZE_COMPRESSED
837 } else {
838 Self::MAX_PAGE_SIZE_UNCOMPRESSED
839 }
840 }
841
842 fn pagination(&self) -> &PaginationParams;
844}
845
846macro_rules! impl_pagination_limits {
853 ($type:ty, compressed = $comp:expr, uncompressed = $uncomp:expr) => {
854 impl $crate::dto::PaginationLimits for $type {
855 const MAX_PAGE_SIZE_COMPRESSED: i64 = $comp;
856 const MAX_PAGE_SIZE_UNCOMPRESSED: i64 = $uncomp;
857
858 fn pagination(&self) -> &$crate::dto::PaginationParams {
859 &self.pagination
860 }
861 }
862 };
863}
864
865#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, Eq, Hash, DeepSizeOf)]
866#[serde(deny_unknown_fields)]
867pub struct PaginationResponse {
868 pub page: i64,
869 pub page_size: i64,
870 pub total: i64,
872}
873
874impl PaginationResponse {
876 pub fn new(page: i64, page_size: i64, total: i64) -> Self {
877 Self { page, page_size, total }
878 }
879
880 pub fn total_pages(&self) -> i64 {
881 (self.total + self.page_size - 1) / self.page_size
883 }
884}
885
886#[derive(
887 Clone, Serialize, Debug, Default, Deserialize, PartialEq, ToSchema, Eq, Hash, DeepSizeOf,
888)]
889#[serde(deny_unknown_fields)]
890pub struct StateRequestBody {
891 #[serde(alias = "contractIds")]
893 #[schema(value_type=Option<Vec<String>>)]
894 pub contract_ids: Option<Vec<Bytes>>,
895 #[serde(alias = "protocolSystem", default)]
898 pub protocol_system: String,
899 #[serde(default = "VersionParam::default")]
900 pub version: VersionParam,
901 #[serde(default)]
902 pub chain: Chain,
903 #[serde(default)]
904 pub pagination: PaginationParams,
905}
906
907impl_pagination_limits!(StateRequestBody, compressed = 1200, uncompressed = 100);
909
910impl StateRequestBody {
911 pub fn new(
912 contract_ids: Option<Vec<Bytes>>,
913 protocol_system: String,
914 version: VersionParam,
915 chain: Chain,
916 pagination: PaginationParams,
917 ) -> Self {
918 Self { contract_ids, protocol_system, version, chain, pagination }
919 }
920
921 pub fn from_block(protocol_system: &str, block: BlockParam) -> Self {
922 Self {
923 contract_ids: None,
924 protocol_system: protocol_system.to_string(),
925 version: VersionParam { timestamp: None, block: Some(block.clone()) },
926 chain: block.chain.unwrap_or_default(),
927 pagination: PaginationParams::default(),
928 }
929 }
930
931 pub fn from_timestamp(protocol_system: &str, timestamp: NaiveDateTime, chain: Chain) -> Self {
932 Self {
933 contract_ids: None,
934 protocol_system: protocol_system.to_string(),
935 version: VersionParam { timestamp: Some(timestamp), block: None },
936 chain,
937 pagination: PaginationParams::default(),
938 }
939 }
940}
941
942#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, DeepSizeOf)]
944pub struct StateRequestResponse {
945 pub accounts: Vec<ResponseAccount>,
946 pub pagination: PaginationResponse,
947}
948
949impl StateRequestResponse {
950 pub fn new(accounts: Vec<ResponseAccount>, pagination: PaginationResponse) -> Self {
951 Self { accounts, pagination }
952 }
953}
954
955#[derive(PartialEq, Clone, Serialize, Deserialize, Default, ToSchema, DeepSizeOf)]
956#[serde(rename = "Account")]
957pub struct ResponseAccount {
961 pub chain: Chain,
962 #[schema(value_type=String, example="0xc9f2e6ea1637E499406986ac50ddC92401ce1f58")]
964 #[serde(with = "hex_bytes")]
965 pub address: Bytes,
966 #[schema(value_type=String, example="Protocol Vault")]
968 pub title: String,
969 #[schema(value_type=HashMap<String, String>, example=json!({"0x....": "0x...."}))]
971 #[serde(with = "hex_hashmap_key_value")]
972 pub slots: HashMap<Bytes, Bytes>,
973 #[schema(value_type=String, example="0x00")]
975 #[serde(with = "hex_bytes")]
976 pub native_balance: Bytes,
977 #[schema(value_type=HashMap<String, String>, example=json!({"0x....": "0x...."}))]
980 #[serde(with = "hex_hashmap_key_value")]
981 pub token_balances: HashMap<Bytes, Bytes>,
982 #[schema(value_type=String, example="0xBADBABE")]
984 #[serde(with = "hex_bytes")]
985 pub code: Bytes,
986 #[schema(value_type=String, example="0x123456789")]
988 #[serde(with = "hex_bytes")]
989 pub code_hash: Bytes,
990 #[schema(value_type=String, example="0x8f1133bfb054a23aedfe5d25b1d81b96195396d8b88bd5d4bcf865fc1ae2c3f4")]
992 #[serde(with = "hex_bytes")]
993 pub balance_modify_tx: Bytes,
994 #[schema(value_type=String, example="0x8f1133bfb054a23aedfe5d25b1d81b96195396d8b88bd5d4bcf865fc1ae2c3f4")]
996 #[serde(with = "hex_bytes")]
997 pub code_modify_tx: Bytes,
998 #[deprecated(note = "The `creation_tx` field is deprecated.")]
1000 #[schema(value_type=Option<String>, example="0x8f1133bfb054a23aedfe5d25b1d81b96195396d8b88bd5d4bcf865fc1ae2c3f4")]
1001 #[serde(with = "hex_bytes_option")]
1002 pub creation_tx: Option<Bytes>,
1003}
1004
1005impl ResponseAccount {
1006 #[allow(clippy::too_many_arguments)]
1007 pub fn new(
1008 chain: Chain,
1009 address: Bytes,
1010 title: String,
1011 slots: HashMap<Bytes, Bytes>,
1012 native_balance: Bytes,
1013 token_balances: HashMap<Bytes, Bytes>,
1014 code: Bytes,
1015 code_hash: Bytes,
1016 balance_modify_tx: Bytes,
1017 code_modify_tx: Bytes,
1018 creation_tx: Option<Bytes>,
1019 ) -> Self {
1020 Self {
1021 chain,
1022 address,
1023 title,
1024 slots,
1025 native_balance,
1026 token_balances,
1027 code,
1028 code_hash,
1029 balance_modify_tx,
1030 code_modify_tx,
1031 creation_tx,
1032 }
1033 }
1034}
1035
1036impl fmt::Debug for ResponseAccount {
1038 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1039 f.debug_struct("ResponseAccount")
1040 .field("chain", &self.chain)
1041 .field("address", &self.address)
1042 .field("title", &self.title)
1043 .field("slots", &self.slots)
1044 .field("native_balance", &self.native_balance)
1045 .field("token_balances", &self.token_balances)
1046 .field("code", &format!("[{} bytes]", self.code.len()))
1047 .field("code_hash", &self.code_hash)
1048 .field("balance_modify_tx", &self.balance_modify_tx)
1049 .field("code_modify_tx", &self.code_modify_tx)
1050 .field("creation_tx", &self.creation_tx)
1051 .finish()
1052 }
1053}
1054
1055#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
1056pub struct AccountBalance {
1057 #[serde(with = "hex_bytes")]
1058 pub account: Bytes,
1059 #[serde(with = "hex_bytes")]
1060 pub token: Bytes,
1061 #[serde(with = "hex_bytes")]
1062 pub balance: Bytes,
1063 #[serde(with = "hex_bytes")]
1064 pub modify_tx: Bytes,
1065}
1066
1067#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
1068#[serde(deny_unknown_fields)]
1069pub struct ContractId {
1070 #[serde(with = "hex_bytes")]
1071 #[schema(value_type=String)]
1072 pub address: Bytes,
1073 pub chain: Chain,
1074}
1075
1076impl ContractId {
1078 pub fn new(chain: Chain, address: Bytes) -> Self {
1079 Self { address, chain }
1080 }
1081
1082 pub fn address(&self) -> &Bytes {
1083 &self.address
1084 }
1085}
1086
1087impl fmt::Display for ContractId {
1088 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1089 write!(f, "{:?}: 0x{}", self.chain, hex::encode(&self.address))
1090 }
1091}
1092
1093#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema, Eq, Hash)]
1100#[serde(deny_unknown_fields)]
1101pub struct VersionParam {
1102 pub timestamp: Option<NaiveDateTime>,
1103 pub block: Option<BlockParam>,
1104}
1105
1106impl DeepSizeOf for VersionParam {
1107 fn deep_size_of_children(&self, ctx: &mut Context) -> usize {
1108 if let Some(block) = &self.block {
1109 return block.deep_size_of_children(ctx);
1110 }
1111
1112 0
1113 }
1114}
1115
1116impl VersionParam {
1117 pub fn new(timestamp: Option<NaiveDateTime>, block: Option<BlockParam>) -> Self {
1118 Self { timestamp, block }
1119 }
1120
1121 pub fn at_block(chain: Chain, block_number: u64) -> Self {
1122 Self::new(
1123 None,
1124 Some({
1125 #[allow(deprecated)]
1126 BlockParam { hash: None, chain: Some(chain), number: Some(block_number as i64) }
1127 }),
1128 )
1129 }
1130}
1131
1132impl Default for VersionParam {
1133 fn default() -> Self {
1134 VersionParam { timestamp: Some(Utc::now().naive_utc()), block: None }
1135 }
1136}
1137
1138#[deprecated(note = "Use StateRequestBody instead")]
1139#[derive(Serialize, Deserialize, Default, Debug, IntoParams)]
1140pub struct StateRequestParameters {
1141 #[param(default = 0)]
1143 pub tvl_gt: Option<u64>,
1144 #[param(default = 0)]
1146 pub inertia_min_gt: Option<u64>,
1147 #[serde(default = "default_include_balances_flag")]
1149 pub include_balances: bool,
1150 #[serde(default)]
1151 pub pagination: PaginationParams,
1152}
1153
1154impl StateRequestParameters {
1155 pub fn new(include_balances: bool) -> Self {
1156 Self {
1157 tvl_gt: None,
1158 inertia_min_gt: None,
1159 include_balances,
1160 pagination: PaginationParams::default(),
1161 }
1162 }
1163
1164 pub fn to_query_string(&self) -> String {
1165 let mut parts = vec![format!("include_balances={}", self.include_balances)];
1166
1167 if let Some(tvl_gt) = self.tvl_gt {
1168 parts.push(format!("tvl_gt={tvl_gt}"));
1169 }
1170
1171 if let Some(inertia) = self.inertia_min_gt {
1172 parts.push(format!("inertia_min_gt={inertia}"));
1173 }
1174
1175 let mut res = parts.join("&");
1176 if !res.is_empty() {
1177 res = format!("?{res}");
1178 }
1179 res
1180 }
1181}
1182
1183#[derive(
1184 Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Hash, Clone, DeepSizeOf,
1185)]
1186#[serde(deny_unknown_fields)]
1187pub struct TokensRequestBody {
1188 #[serde(alias = "tokenAddresses")]
1190 #[schema(value_type=Option<Vec<String>>)]
1191 pub token_addresses: Option<Vec<Bytes>>,
1192 #[serde(default)]
1200 pub min_quality: Option<i32>,
1201 #[serde(default)]
1203 pub traded_n_days_ago: Option<u64>,
1204 #[serde(default)]
1206 pub pagination: PaginationParams,
1207 #[serde(default)]
1209 pub chain: Chain,
1210}
1211
1212impl_pagination_limits!(TokensRequestBody, compressed = 12900, uncompressed = 3000);
1214
1215#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, Eq, Hash, DeepSizeOf)]
1217pub struct TokensRequestResponse {
1218 pub tokens: Vec<ResponseToken>,
1219 pub pagination: PaginationResponse,
1220}
1221
1222impl TokensRequestResponse {
1223 pub fn new(tokens: Vec<ResponseToken>, pagination_request: &PaginationResponse) -> Self {
1224 Self { tokens, pagination: pagination_request.clone() }
1225 }
1226}
1227
1228#[derive(
1229 PartialEq, Debug, Clone, Serialize, Deserialize, Default, ToSchema, Eq, Hash, DeepSizeOf,
1230)]
1231#[serde(rename = "Token")]
1232pub struct ResponseToken {
1234 pub chain: Chain,
1235 #[schema(value_type=String, example="0xc9f2e6ea1637E499406986ac50ddC92401ce1f58")]
1237 #[serde(with = "hex_bytes")]
1238 pub address: Bytes,
1239 #[schema(value_type=String, example="WETH")]
1241 pub symbol: String,
1242 pub decimals: u32,
1244 pub tax: u64,
1246 pub gas: Vec<Option<u64>>,
1248 pub quality: u32,
1256}
1257
1258impl From<models::token::Token> for ResponseToken {
1259 fn from(value: models::token::Token) -> Self {
1260 Self {
1261 chain: value.chain.into(),
1262 address: value.address,
1263 symbol: value.symbol,
1264 decimals: value.decimals,
1265 tax: value.tax,
1266 gas: value.gas,
1267 quality: value.quality,
1268 }
1269 }
1270}
1271
1272#[derive(Serialize, Deserialize, Debug, Default, ToSchema, Clone, DeepSizeOf)]
1273#[serde(deny_unknown_fields)]
1274pub struct ProtocolComponentsRequestBody {
1275 pub protocol_system: String,
1278 #[schema(value_type=Option<Vec<String>>)]
1280 #[serde(alias = "componentAddresses")]
1281 pub component_ids: Option<Vec<ComponentId>>,
1282 #[serde(default)]
1285 pub tvl_gt: Option<f64>,
1286 #[serde(default)]
1287 pub chain: Chain,
1288 #[serde(default)]
1290 pub pagination: PaginationParams,
1291}
1292
1293impl_pagination_limits!(ProtocolComponentsRequestBody, compressed = 2550, uncompressed = 500);
1295
1296impl PartialEq for ProtocolComponentsRequestBody {
1298 fn eq(&self, other: &Self) -> bool {
1299 let tvl_close_enough = match (self.tvl_gt, other.tvl_gt) {
1300 (Some(a), Some(b)) => (a - b).abs() < 1e-6,
1301 (None, None) => true,
1302 _ => false,
1303 };
1304
1305 self.protocol_system == other.protocol_system &&
1306 self.component_ids == other.component_ids &&
1307 tvl_close_enough &&
1308 self.chain == other.chain &&
1309 self.pagination == other.pagination
1310 }
1311}
1312
1313impl Eq for ProtocolComponentsRequestBody {}
1315
1316impl Hash for ProtocolComponentsRequestBody {
1317 fn hash<H: Hasher>(&self, state: &mut H) {
1318 self.protocol_system.hash(state);
1319 self.component_ids.hash(state);
1320
1321 if let Some(tvl) = self.tvl_gt {
1323 tvl.to_bits().hash(state);
1325 } else {
1326 state.write_u8(0);
1328 }
1329
1330 self.chain.hash(state);
1331 self.pagination.hash(state);
1332 }
1333}
1334
1335impl ProtocolComponentsRequestBody {
1336 pub fn system_filtered(system: &str, tvl_gt: Option<f64>, chain: Chain) -> Self {
1337 Self {
1338 protocol_system: system.to_string(),
1339 component_ids: None,
1340 tvl_gt,
1341 chain,
1342 pagination: Default::default(),
1343 }
1344 }
1345
1346 pub fn id_filtered(system: &str, ids: Vec<String>, chain: Chain) -> Self {
1347 Self {
1348 protocol_system: system.to_string(),
1349 component_ids: Some(ids),
1350 tvl_gt: None,
1351 chain,
1352 pagination: Default::default(),
1353 }
1354 }
1355}
1356
1357impl ProtocolComponentsRequestBody {
1358 pub fn new(
1359 protocol_system: String,
1360 component_ids: Option<Vec<String>>,
1361 tvl_gt: Option<f64>,
1362 chain: Chain,
1363 pagination: PaginationParams,
1364 ) -> Self {
1365 Self { protocol_system, component_ids, tvl_gt, chain, pagination }
1366 }
1367}
1368
1369#[deprecated(note = "Use ProtocolComponentsRequestBody instead")]
1370#[derive(Serialize, Deserialize, Default, Debug, IntoParams)]
1371pub struct ProtocolComponentRequestParameters {
1372 #[param(default = 0)]
1374 pub tvl_gt: Option<f64>,
1375}
1376
1377impl ProtocolComponentRequestParameters {
1378 pub fn tvl_filtered(min_tvl: f64) -> Self {
1379 Self { tvl_gt: Some(min_tvl) }
1380 }
1381}
1382
1383impl ProtocolComponentRequestParameters {
1384 pub fn to_query_string(&self) -> String {
1385 if let Some(tvl_gt) = self.tvl_gt {
1386 return format!("?tvl_gt={tvl_gt}");
1387 }
1388 String::new()
1389 }
1390}
1391
1392#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, DeepSizeOf)]
1394pub struct ProtocolComponentRequestResponse {
1395 pub protocol_components: Vec<ProtocolComponent>,
1396 pub pagination: PaginationResponse,
1397}
1398
1399impl ProtocolComponentRequestResponse {
1400 pub fn new(
1401 protocol_components: Vec<ProtocolComponent>,
1402 pagination: PaginationResponse,
1403 ) -> Self {
1404 Self { protocol_components, pagination }
1405 }
1406}
1407
1408#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema, Eq, Hash)]
1409#[serde(deny_unknown_fields)]
1410#[deprecated]
1411pub struct ProtocolId {
1412 pub id: String,
1413 pub chain: Chain,
1414}
1415
1416impl From<ProtocolId> for String {
1417 fn from(protocol_id: ProtocolId) -> Self {
1418 protocol_id.id
1419 }
1420}
1421
1422impl AsRef<str> for ProtocolId {
1423 fn as_ref(&self) -> &str {
1424 &self.id
1425 }
1426}
1427
1428#[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize, ToSchema, DeepSizeOf)]
1430pub struct ResponseProtocolState {
1431 pub component_id: String,
1433 #[schema(value_type=HashMap<String, String>)]
1436 #[serde(with = "hex_hashmap_value")]
1437 pub attributes: HashMap<String, Bytes>,
1438 #[schema(value_type=HashMap<String, String>)]
1440 #[serde(with = "hex_hashmap_key_value")]
1441 pub balances: HashMap<Bytes, Bytes>,
1442}
1443
1444impl From<models::protocol::ProtocolComponentState> for ResponseProtocolState {
1445 fn from(value: models::protocol::ProtocolComponentState) -> Self {
1446 Self {
1447 component_id: value.component_id,
1448 attributes: value.attributes,
1449 balances: value.balances,
1450 }
1451 }
1452}
1453
1454fn default_include_balances_flag() -> bool {
1455 true
1456}
1457
1458#[derive(Clone, Debug, Serialize, PartialEq, ToSchema, Default, Eq, Hash, DeepSizeOf)]
1460#[serde(deny_unknown_fields)]
1461pub struct ProtocolStateRequestBody {
1462 #[serde(alias = "protocolIds")]
1464 pub protocol_ids: Option<Vec<String>>,
1465 #[serde(alias = "protocolSystem")]
1468 pub protocol_system: String,
1469 #[serde(default)]
1470 pub chain: Chain,
1471 #[serde(default = "default_include_balances_flag")]
1473 pub include_balances: bool,
1474 #[serde(default = "VersionParam::default")]
1475 pub version: VersionParam,
1476 #[serde(default)]
1477 pub pagination: PaginationParams,
1478}
1479
1480impl_pagination_limits!(ProtocolStateRequestBody, compressed = 360, uncompressed = 100);
1482
1483impl ProtocolStateRequestBody {
1484 pub fn id_filtered<I, T>(ids: I) -> Self
1485 where
1486 I: IntoIterator<Item = T>,
1487 T: Into<String>,
1488 {
1489 Self {
1490 protocol_ids: Some(
1491 ids.into_iter()
1492 .map(Into::into)
1493 .collect(),
1494 ),
1495 ..Default::default()
1496 }
1497 }
1498}
1499
1500impl<'de> Deserialize<'de> for ProtocolStateRequestBody {
1504 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1505 where
1506 D: Deserializer<'de>,
1507 {
1508 #[derive(Deserialize)]
1509 #[serde(untagged)]
1510 enum ProtocolIdOrString {
1511 Old(Vec<ProtocolId>),
1512 New(Vec<String>),
1513 }
1514
1515 struct ProtocolStateRequestBodyVisitor;
1516
1517 impl<'de> de::Visitor<'de> for ProtocolStateRequestBodyVisitor {
1518 type Value = ProtocolStateRequestBody;
1519
1520 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1521 formatter.write_str("struct ProtocolStateRequestBody")
1522 }
1523
1524 fn visit_map<V>(self, mut map: V) -> Result<ProtocolStateRequestBody, V::Error>
1525 where
1526 V: de::MapAccess<'de>,
1527 {
1528 let mut protocol_ids = None;
1529 let mut protocol_system = None;
1530 let mut version = None;
1531 let mut chain = None;
1532 let mut include_balances = None;
1533 let mut pagination = None;
1534
1535 while let Some(key) = map.next_key::<String>()? {
1536 match key.as_str() {
1537 "protocol_ids" | "protocolIds" => {
1538 let value: ProtocolIdOrString = map.next_value()?;
1539 protocol_ids = match value {
1540 ProtocolIdOrString::Old(ids) => {
1541 Some(ids.into_iter().map(|p| p.id).collect())
1542 }
1543 ProtocolIdOrString::New(ids_str) => Some(ids_str),
1544 };
1545 }
1546 "protocol_system" | "protocolSystem" => {
1547 protocol_system = Some(map.next_value()?);
1548 }
1549 "version" => {
1550 version = Some(map.next_value()?);
1551 }
1552 "chain" => {
1553 chain = Some(map.next_value()?);
1554 }
1555 "include_balances" => {
1556 include_balances = Some(map.next_value()?);
1557 }
1558 "pagination" => {
1559 pagination = Some(map.next_value()?);
1560 }
1561 _ => {
1562 return Err(de::Error::unknown_field(
1563 &key,
1564 &[
1565 "contract_ids",
1566 "protocol_system",
1567 "version",
1568 "chain",
1569 "include_balances",
1570 "pagination",
1571 ],
1572 ))
1573 }
1574 }
1575 }
1576
1577 Ok(ProtocolStateRequestBody {
1578 protocol_ids,
1579 protocol_system: protocol_system.unwrap_or_default(),
1580 version: version.unwrap_or_else(VersionParam::default),
1581 chain: chain.unwrap_or_else(Chain::default),
1582 include_balances: include_balances.unwrap_or(true),
1583 pagination: pagination.unwrap_or_else(PaginationParams::default),
1584 })
1585 }
1586 }
1587
1588 deserializer.deserialize_struct(
1589 "ProtocolStateRequestBody",
1590 &[
1591 "contract_ids",
1592 "protocol_system",
1593 "version",
1594 "chain",
1595 "include_balances",
1596 "pagination",
1597 ],
1598 ProtocolStateRequestBodyVisitor,
1599 )
1600 }
1601}
1602
1603#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, DeepSizeOf)]
1604pub struct ProtocolStateRequestResponse {
1605 pub states: Vec<ResponseProtocolState>,
1606 pub pagination: PaginationResponse,
1607}
1608
1609impl ProtocolStateRequestResponse {
1610 pub fn new(states: Vec<ResponseProtocolState>, pagination: PaginationResponse) -> Self {
1611 Self { states, pagination }
1612 }
1613}
1614
1615#[derive(Serialize, Clone, PartialEq, Hash, Eq)]
1616pub struct ProtocolComponentId {
1617 pub chain: Chain,
1618 pub system: String,
1619 pub id: String,
1620}
1621
1622#[derive(Debug, Serialize, ToSchema)]
1623#[serde(tag = "status", content = "message")]
1624#[schema(example = json!({"status": "NotReady", "message": "No db connection"}))]
1625pub enum Health {
1626 Ready,
1627 Starting(String),
1628 NotReady(String),
1629}
1630
1631#[derive(Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Hash, Clone)]
1632#[serde(deny_unknown_fields)]
1633pub struct ProtocolSystemsRequestBody {
1634 #[serde(default)]
1635 pub chain: Chain,
1636 #[serde(default)]
1637 pub pagination: PaginationParams,
1638}
1639
1640impl_pagination_limits!(ProtocolSystemsRequestBody, compressed = 100, uncompressed = 100);
1642
1643#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, Eq, Hash)]
1644pub struct ProtocolSystemsRequestResponse {
1645 pub protocol_systems: Vec<String>,
1647 #[serde(default)]
1650 pub dci_protocols: Vec<String>,
1651 pub pagination: PaginationResponse,
1652}
1653
1654impl ProtocolSystemsRequestResponse {
1655 pub fn new(
1656 protocol_systems: Vec<String>,
1657 dci_protocols: Vec<String>,
1658 pagination: PaginationResponse,
1659 ) -> Self {
1660 Self { protocol_systems, dci_protocols, pagination }
1661 }
1662}
1663
1664#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
1665pub struct DCIUpdate {
1666 pub new_entrypoints: HashMap<ComponentId, HashSet<EntryPoint>>,
1668 pub new_entrypoint_params: HashMap<String, HashSet<(TracingParams, String)>>,
1671 pub trace_results: HashMap<String, TracingResult>,
1673}
1674
1675impl From<models::blockchain::DCIUpdate> for DCIUpdate {
1676 fn from(value: models::blockchain::DCIUpdate) -> Self {
1677 Self {
1678 new_entrypoints: value
1679 .new_entrypoints
1680 .into_iter()
1681 .map(|(k, v)| {
1682 (
1683 k,
1684 v.into_iter()
1685 .map(|v| v.into())
1686 .collect(),
1687 )
1688 })
1689 .collect(),
1690 new_entrypoint_params: value
1691 .new_entrypoint_params
1692 .into_iter()
1693 .map(|(k, v)| {
1694 (
1695 k,
1696 v.into_iter()
1697 .map(|(params, i)| (params.into(), i))
1698 .collect(),
1699 )
1700 })
1701 .collect(),
1702 trace_results: value
1703 .trace_results
1704 .into_iter()
1705 .map(|(k, v)| (k, v.into()))
1706 .collect(),
1707 }
1708 }
1709}
1710
1711#[derive(Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Hash, Clone)]
1712#[serde(deny_unknown_fields)]
1713pub struct ComponentTvlRequestBody {
1714 #[serde(default)]
1715 pub chain: Chain,
1716 #[serde(alias = "protocolSystem")]
1719 pub protocol_system: Option<String>,
1720 #[serde(default)]
1721 pub component_ids: Option<Vec<String>>,
1722 #[serde(default)]
1723 pub pagination: PaginationParams,
1724}
1725
1726impl_pagination_limits!(ComponentTvlRequestBody, compressed = 100, uncompressed = 100);
1728
1729impl ComponentTvlRequestBody {
1730 pub fn system_filtered(system: &str, chain: Chain) -> Self {
1731 Self {
1732 chain,
1733 protocol_system: Some(system.to_string()),
1734 component_ids: None,
1735 pagination: Default::default(),
1736 }
1737 }
1738
1739 pub fn id_filtered(ids: Vec<String>, chain: Chain) -> Self {
1740 Self {
1741 chain,
1742 protocol_system: None,
1743 component_ids: Some(ids),
1744 pagination: Default::default(),
1745 }
1746 }
1747}
1748#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema)]
1750pub struct ComponentTvlRequestResponse {
1751 pub tvl: HashMap<String, f64>,
1752 pub pagination: PaginationResponse,
1753}
1754
1755impl ComponentTvlRequestResponse {
1756 pub fn new(tvl: HashMap<String, f64>, pagination: PaginationResponse) -> Self {
1757 Self { tvl, pagination }
1758 }
1759}
1760
1761#[derive(
1762 Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Hash, Clone, DeepSizeOf,
1763)]
1764pub struct TracedEntryPointRequestBody {
1765 #[serde(default)]
1766 pub chain: Chain,
1767 pub protocol_system: String,
1770 #[schema(value_type = Option<Vec<String>>)]
1772 pub component_ids: Option<Vec<ComponentId>>,
1773 #[serde(default)]
1775 pub pagination: PaginationParams,
1776}
1777
1778impl_pagination_limits!(TracedEntryPointRequestBody, compressed = 100, uncompressed = 100);
1780
1781#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema, Eq, Hash, DeepSizeOf)]
1782pub struct EntryPoint {
1783 #[schema(example = "0xEdf63cce4bA70cbE74064b7687882E71ebB0e988:getRate()")]
1784 pub external_id: String,
1786 #[schema(value_type=String, example="0x8f4E8439b970363648421C692dd897Fb9c0Bd1D9")]
1787 #[serde(with = "hex_bytes")]
1788 pub target: Bytes,
1790 #[schema(example = "getRate()")]
1791 pub signature: String,
1793}
1794
1795#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema, Eq, Hash, DeepSizeOf)]
1796pub enum StorageOverride {
1797 #[schema(value_type=HashMap<String, String>)]
1801 Diff(BTreeMap<StoreKey, StoreVal>),
1802
1803 #[schema(value_type=HashMap<String, String>)]
1807 Replace(BTreeMap<StoreKey, StoreVal>),
1808}
1809
1810impl From<models::blockchain::StorageOverride> for StorageOverride {
1811 fn from(value: models::blockchain::StorageOverride) -> Self {
1812 match value {
1813 models::blockchain::StorageOverride::Diff(diff) => StorageOverride::Diff(diff),
1814 models::blockchain::StorageOverride::Replace(replace) => {
1815 StorageOverride::Replace(replace)
1816 }
1817 }
1818 }
1819}
1820
1821#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema, Eq, Hash, DeepSizeOf)]
1826pub struct AccountOverrides {
1827 pub slots: Option<StorageOverride>,
1829 #[schema(value_type=Option<String>)]
1830 pub native_balance: Option<Balance>,
1832 #[schema(value_type=Option<String>)]
1833 pub code: Option<Code>,
1835}
1836
1837impl From<models::blockchain::AccountOverrides> for AccountOverrides {
1838 fn from(value: models::blockchain::AccountOverrides) -> Self {
1839 AccountOverrides {
1840 slots: value.slots.map(|s| s.into()),
1841 native_balance: value.native_balance,
1842 code: value.code,
1843 }
1844 }
1845}
1846
1847#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema, Eq, Hash, DeepSizeOf)]
1848pub struct RPCTracerParams {
1849 #[schema(value_type=Option<String>)]
1852 #[serde(with = "hex_bytes_option", default)]
1853 pub caller: Option<Bytes>,
1854 #[schema(value_type=String, example="0x679aefce")]
1856 #[serde(with = "hex_bytes")]
1857 pub calldata: Bytes,
1858 pub state_overrides: Option<BTreeMap<Address, AccountOverrides>>,
1860 #[schema(value_type=Option<Vec<String>>)]
1863 #[serde(default)]
1864 pub prune_addresses: Option<Vec<Address>>,
1865}
1866
1867impl From<models::blockchain::RPCTracerParams> for RPCTracerParams {
1868 fn from(value: models::blockchain::RPCTracerParams) -> Self {
1869 RPCTracerParams {
1870 caller: value.caller,
1871 calldata: value.calldata,
1872 state_overrides: value.state_overrides.map(|overrides| {
1873 overrides
1874 .into_iter()
1875 .map(|(address, account_overrides)| (address, account_overrides.into()))
1876 .collect()
1877 }),
1878 prune_addresses: value.prune_addresses,
1879 }
1880 }
1881}
1882
1883#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Hash, DeepSizeOf, ToSchema)]
1884#[serde(tag = "method", rename_all = "lowercase")]
1885pub enum TracingParams {
1886 RPCTracer(RPCTracerParams),
1888}
1889
1890impl From<models::blockchain::TracingParams> for TracingParams {
1891 fn from(value: models::blockchain::TracingParams) -> Self {
1892 match value {
1893 models::blockchain::TracingParams::RPCTracer(params) => {
1894 TracingParams::RPCTracer(params.into())
1895 }
1896 }
1897 }
1898}
1899
1900impl From<models::blockchain::EntryPoint> for EntryPoint {
1901 fn from(value: models::blockchain::EntryPoint) -> Self {
1902 Self { external_id: value.external_id, target: value.target, signature: value.signature }
1903 }
1904}
1905
1906#[derive(Serialize, Deserialize, Debug, PartialEq, ToSchema, Eq, Clone, DeepSizeOf)]
1907pub struct EntryPointWithTracingParams {
1908 pub entry_point: EntryPoint,
1910 pub params: TracingParams,
1912}
1913
1914impl From<models::blockchain::EntryPointWithTracingParams> for EntryPointWithTracingParams {
1915 fn from(value: models::blockchain::EntryPointWithTracingParams) -> Self {
1916 Self { entry_point: value.entry_point.into(), params: value.params.into() }
1917 }
1918}
1919
1920#[derive(
1921 Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize, DeepSizeOf,
1922)]
1923pub struct AddressStorageLocation {
1924 pub key: StoreKey,
1925 pub offset: u8,
1926}
1927
1928impl AddressStorageLocation {
1929 pub fn new(key: StoreKey, offset: u8) -> Self {
1930 Self { key, offset }
1931 }
1932}
1933
1934impl From<models::blockchain::AddressStorageLocation> for AddressStorageLocation {
1935 fn from(value: models::blockchain::AddressStorageLocation) -> Self {
1936 Self { key: value.key, offset: value.offset }
1937 }
1938}
1939
1940fn deserialize_retriggers_from_value(
1941 value: &serde_json::Value,
1942) -> Result<HashSet<(StoreKey, AddressStorageLocation)>, String> {
1943 use serde::Deserialize;
1944 use serde_json::Value;
1945
1946 let mut result = HashSet::new();
1947
1948 if let Value::Array(items) = value {
1949 for item in items {
1950 if let Value::Array(pair) = item {
1951 if pair.len() == 2 {
1952 let key = StoreKey::deserialize(&pair[0])
1953 .map_err(|e| format!("Failed to deserialize key: {}", e))?;
1954
1955 let addr_storage = match &pair[1] {
1957 Value::String(_) => {
1958 let storage_key = StoreKey::deserialize(&pair[1]).map_err(|e| {
1960 format!("Failed to deserialize old format storage key: {}", e)
1961 })?;
1962 AddressStorageLocation::new(storage_key, 12)
1963 }
1964 Value::Object(_) => {
1965 AddressStorageLocation::deserialize(&pair[1]).map_err(|e| {
1967 format!("Failed to deserialize AddressStorageLocation: {}", e)
1968 })?
1969 }
1970 _ => return Err("Invalid retrigger format".to_string()),
1971 };
1972
1973 result.insert((key, addr_storage));
1974 }
1975 }
1976 }
1977 }
1978
1979 Ok(result)
1980}
1981
1982#[derive(Serialize, Debug, Default, PartialEq, ToSchema, Eq, Clone, DeepSizeOf)]
1983pub struct TracingResult {
1984 #[schema(value_type=HashSet<(String, String)>)]
1985 pub retriggers: HashSet<(StoreKey, AddressStorageLocation)>,
1986 #[schema(value_type=HashMap<String,HashSet<String>>)]
1987 pub accessed_slots: HashMap<Address, HashSet<StoreKey>>,
1988}
1989
1990impl<'de> Deserialize<'de> for TracingResult {
1993 fn deserialize<D>(deserializer: D) -> Result<TracingResult, D::Error>
1994 where
1995 D: Deserializer<'de>,
1996 {
1997 use serde::de::Error;
1998 use serde_json::Value;
1999
2000 let value = Value::deserialize(deserializer)?;
2001 let mut result = TracingResult::default();
2002
2003 if let Value::Object(map) = value {
2004 if let Some(retriggers_value) = map.get("retriggers") {
2006 result.retriggers =
2007 deserialize_retriggers_from_value(retriggers_value).map_err(|e| {
2008 D::Error::custom(format!("Failed to deserialize retriggers: {}", e))
2009 })?;
2010 }
2011
2012 if let Some(accessed_slots_value) = map.get("accessed_slots") {
2014 result.accessed_slots = serde_json::from_value(accessed_slots_value.clone())
2015 .map_err(|e| {
2016 D::Error::custom(format!("Failed to deserialize accessed_slots: {}", e))
2017 })?;
2018 }
2019 }
2020
2021 Ok(result)
2022 }
2023}
2024
2025impl From<models::blockchain::TracingResult> for TracingResult {
2026 fn from(value: models::blockchain::TracingResult) -> Self {
2027 TracingResult {
2028 retriggers: value
2029 .retriggers
2030 .into_iter()
2031 .map(|(k, v)| (k, v.into()))
2032 .collect(),
2033 accessed_slots: value.accessed_slots,
2034 }
2035 }
2036}
2037
2038#[derive(Serialize, PartialEq, ToSchema, Eq, Clone, Debug, Deserialize, DeepSizeOf)]
2039pub struct TracedEntryPointRequestResponse {
2040 #[schema(value_type = HashMap<String, Vec<(EntryPointWithTracingParams, TracingResult)>>)]
2043 pub traced_entry_points:
2044 HashMap<ComponentId, Vec<(EntryPointWithTracingParams, TracingResult)>>,
2045 pub pagination: PaginationResponse,
2046}
2047
2048#[derive(Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Clone)]
2049pub struct AddEntryPointRequestBody {
2050 #[serde(default)]
2051 pub chain: Chain,
2052 #[schema(value_type=String)]
2053 #[serde(default)]
2054 pub block_hash: Bytes,
2055 #[schema(value_type = Vec<(String, Vec<EntryPointWithTracingParams>)>)]
2057 pub entry_points_with_tracing_data: Vec<(ComponentId, Vec<EntryPointWithTracingParams>)>,
2058}
2059
2060#[derive(Serialize, PartialEq, ToSchema, Eq, Clone, Debug, Deserialize)]
2061pub struct AddEntryPointRequestResponse {
2062 #[schema(value_type = HashMap<String, Vec<(EntryPointWithTracingParams, TracingResult)>>)]
2065 pub traced_entry_points:
2066 HashMap<ComponentId, Vec<(EntryPointWithTracingParams, TracingResult)>>,
2067}
2068
2069#[cfg(test)]
2070mod test {
2071 use std::str::FromStr;
2072
2073 use maplit::hashmap;
2074 use rstest::rstest;
2075
2076 use super::*;
2077
2078 #[rstest]
2081 #[case::legacy_format(None, false)]
2082 #[case::explicit_true(Some(true), true)]
2083 #[case::explicit_false(Some(false), false)]
2084 fn test_subscribe_compression_backward_compatibility(
2085 #[case] compression: Option<bool>,
2086 #[case] expected: bool,
2087 ) {
2088 use serde_json::json;
2089
2090 let mut json_value = json!({
2091 "method": "subscribe",
2092 "extractor_id": {
2093 "chain": "ethereum",
2094 "name": "test"
2095 },
2096 "include_state": true
2097 });
2098
2099 if let Some(value) = compression {
2100 json_value["compression"] = json!(value);
2101 }
2102
2103 let command: Command =
2104 serde_json::from_value(json_value).expect("Failed to deserialize Subscribe command");
2105
2106 if let Command::Subscribe { compression, .. } = command {
2107 assert_eq!(compression, expected);
2108 } else {
2109 panic!("Expected Subscribe command");
2110 }
2111 }
2112
2113 #[rstest]
2116 #[case::legacy_format(None, false)]
2117 #[case::explicit_true(Some(true), true)]
2118 #[case::explicit_false(Some(false), false)]
2119 fn test_subscribe_partial_blocks_backward_compatibility(
2120 #[case] partial_blocks: Option<bool>,
2121 #[case] expected: bool,
2122 ) {
2123 use serde_json::json;
2124
2125 let mut json_value = json!({
2126 "method": "subscribe",
2127 "extractor_id": {
2128 "chain": "ethereum",
2129 "name": "test"
2130 },
2131 "include_state": true
2132 });
2133
2134 if let Some(value) = partial_blocks {
2135 json_value["partial_blocks"] = json!(value);
2136 }
2137
2138 let command: Command =
2139 serde_json::from_value(json_value).expect("Failed to deserialize Subscribe command");
2140
2141 if let Command::Subscribe { partial_blocks, .. } = command {
2142 assert_eq!(partial_blocks, expected);
2143 } else {
2144 panic!("Expected Subscribe command");
2145 }
2146 }
2147
2148 #[rstest]
2151 #[case::legacy_format(None, vec![])]
2152 #[case::with_dci(Some(vec!["vm:curve"]), vec!["vm:curve"])]
2153 #[case::empty_dci(Some(vec![]), vec![])]
2154 fn test_protocol_systems_dci_backward_compatibility(
2155 #[case] dci_protocols: Option<Vec<&str>>,
2156 #[case] expected: Vec<&str>,
2157 ) {
2158 use serde_json::json;
2159
2160 let mut json_value = json!({
2161 "protocol_systems": ["uniswap_v2", "vm:curve"],
2162 "pagination": { "page": 0, "page_size": 20, "total": 2 }
2163 });
2164
2165 if let Some(dci) = dci_protocols {
2166 json_value["dci_protocols"] = json!(dci);
2167 }
2168
2169 let resp: ProtocolSystemsRequestResponse =
2170 serde_json::from_value(json_value).expect("Failed to deserialize response");
2171
2172 assert_eq!(resp.dci_protocols, expected);
2173
2174 let serialized = serde_json::to_string(&resp).unwrap();
2176 let round_tripped: ProtocolSystemsRequestResponse =
2177 serde_json::from_str(&serialized).unwrap();
2178 assert_eq!(resp, round_tripped);
2179 }
2180
2181 #[test]
2182 fn test_tracing_result_backward_compatibility() {
2183 use serde_json::json;
2184
2185 let old_format_json = json!({
2187 "retriggers": [
2188 ["0x01", "0x02"],
2189 ["0x03", "0x04"]
2190 ],
2191 "accessed_slots": {
2192 "0x05": ["0x06", "0x07"]
2193 }
2194 });
2195
2196 let result: TracingResult = serde_json::from_value(old_format_json).unwrap();
2197
2198 assert_eq!(result.retriggers.len(), 2);
2200 let retriggers_vec: Vec<_> = result.retriggers.iter().collect();
2201 assert!(retriggers_vec.iter().any(|(k, v)| {
2202 k == &Bytes::from("0x01") && v.key == Bytes::from("0x02") && v.offset == 12
2203 }));
2204 assert!(retriggers_vec.iter().any(|(k, v)| {
2205 k == &Bytes::from("0x03") && v.key == Bytes::from("0x04") && v.offset == 12
2206 }));
2207
2208 let new_format_json = json!({
2210 "retriggers": [
2211 ["0x01", {"key": "0x02", "offset": 12}],
2212 ["0x03", {"key": "0x04", "offset": 5}]
2213 ],
2214 "accessed_slots": {
2215 "0x05": ["0x06", "0x07"]
2216 }
2217 });
2218
2219 let result2: TracingResult = serde_json::from_value(new_format_json).unwrap();
2220
2221 assert_eq!(result2.retriggers.len(), 2);
2223 let retriggers_vec2: Vec<_> = result2.retriggers.iter().collect();
2224 assert!(retriggers_vec2.iter().any(|(k, v)| {
2225 k == &Bytes::from("0x01") && v.key == Bytes::from("0x02") && v.offset == 12
2226 }));
2227 assert!(retriggers_vec2.iter().any(|(k, v)| {
2228 k == &Bytes::from("0x03") && v.key == Bytes::from("0x04") && v.offset == 5
2229 }));
2230 }
2231
2232 #[rstest]
2233 #[case::legacy_format(None, None)]
2234 #[case::full_block(Some(None), None)]
2235 #[case::partial_block(Some(Some(1)), Some(1))]
2236 fn test_block_changes_is_partial_backward_compatibility(
2237 #[case] has_partial_value: Option<Option<u32>>,
2238 #[case] expected: Option<u32>,
2239 ) {
2240 use serde_json::json;
2241
2242 let mut json_value = json!({
2243 "extractor": "test_extractor",
2244 "chain": "ethereum",
2245 "block": {
2246 "number": 100,
2247 "hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
2248 "parent_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
2249 "chain": "ethereum",
2250 "ts": "2024-01-01T00:00:00"
2251 },
2252 "finalized_block_height": 99,
2253 "revert": false,
2254 "new_tokens": {},
2255 "account_updates": {},
2256 "state_updates": {},
2257 "new_protocol_components": {},
2258 "deleted_protocol_components": {},
2259 "component_balances": {},
2260 "account_balances": {},
2261 "component_tvl": {},
2262 "dci_update": {
2263 "new_entrypoints": {},
2264 "new_entrypoint_params": {},
2265 "trace_results": {}
2266 }
2267 });
2268
2269 if let Some(partial_value) = has_partial_value {
2271 json_value["partial_block_index"] = json!(partial_value);
2272 }
2273
2274 let block_changes: BlockAggregatedChanges = serde_json::from_value(json_value)
2275 .expect("Failed to deserialize BlockAggregatedChanges");
2276
2277 assert_eq!(block_changes.partial_block_index, expected);
2278 }
2279
2280 #[test]
2281 fn test_protocol_components_equality() {
2282 let body1 = ProtocolComponentsRequestBody {
2283 protocol_system: "protocol1".to_string(),
2284 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
2285 tvl_gt: Some(1000.0),
2286 chain: Chain::Ethereum,
2287 pagination: PaginationParams::default(),
2288 };
2289
2290 let body2 = ProtocolComponentsRequestBody {
2291 protocol_system: "protocol1".to_string(),
2292 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
2293 tvl_gt: Some(1000.0 + 1e-7), chain: Chain::Ethereum,
2295 pagination: PaginationParams::default(),
2296 };
2297
2298 assert_eq!(body1, body2);
2300 }
2301
2302 #[test]
2303 fn test_protocol_components_inequality() {
2304 let body1 = ProtocolComponentsRequestBody {
2305 protocol_system: "protocol1".to_string(),
2306 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
2307 tvl_gt: Some(1000.0),
2308 chain: Chain::Ethereum,
2309 pagination: PaginationParams::default(),
2310 };
2311
2312 let body2 = ProtocolComponentsRequestBody {
2313 protocol_system: "protocol1".to_string(),
2314 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
2315 tvl_gt: Some(1000.0 + 1e-5), chain: Chain::Ethereum,
2317 pagination: PaginationParams::default(),
2318 };
2319
2320 assert_ne!(body1, body2);
2322 }
2323
2324 #[test]
2325 fn test_parse_state_request() {
2326 let json_str = r#"
2327 {
2328 "contractIds": [
2329 "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092"
2330 ],
2331 "protocol_system": "uniswap_v2",
2332 "version": {
2333 "timestamp": "2069-01-01T04:20:00",
2334 "block": {
2335 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2336 "number": 213,
2337 "chain": "ethereum"
2338 }
2339 }
2340 }
2341 "#;
2342
2343 let result: StateRequestBody = serde_json::from_str(json_str).unwrap();
2344
2345 let contract0 = "b4eccE46b8D4e4abFd03C9B806276A6735C9c092"
2346 .parse()
2347 .unwrap();
2348 let block_hash = "24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4"
2349 .parse()
2350 .unwrap();
2351 let block_number = 213;
2352
2353 let expected_timestamp =
2354 NaiveDateTime::parse_from_str("2069-01-01T04:20:00", "%Y-%m-%dT%H:%M:%S").unwrap();
2355
2356 let expected = StateRequestBody {
2357 contract_ids: Some(vec![contract0]),
2358 protocol_system: "uniswap_v2".to_string(),
2359 version: VersionParam {
2360 timestamp: Some(expected_timestamp),
2361 block: Some(BlockParam {
2362 hash: Some(block_hash),
2363 chain: Some(Chain::Ethereum),
2364 number: Some(block_number),
2365 }),
2366 },
2367 chain: Chain::Ethereum,
2368 pagination: PaginationParams::default(),
2369 };
2370
2371 assert_eq!(result, expected);
2372 }
2373
2374 #[test]
2375 fn test_parse_state_request_dual_interface() {
2376 let json_common = r#"
2377 {
2378 "__CONTRACT_IDS__": [
2379 "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092"
2380 ],
2381 "version": {
2382 "timestamp": "2069-01-01T04:20:00",
2383 "block": {
2384 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2385 "number": 213,
2386 "chain": "ethereum"
2387 }
2388 }
2389 }
2390 "#;
2391
2392 let json_str_snake = json_common.replace("\"__CONTRACT_IDS__\"", "\"contract_ids\"");
2393 let json_str_camel = json_common.replace("\"__CONTRACT_IDS__\"", "\"contractIds\"");
2394
2395 let snake: StateRequestBody = serde_json::from_str(&json_str_snake).unwrap();
2396 let camel: StateRequestBody = serde_json::from_str(&json_str_camel).unwrap();
2397
2398 assert_eq!(snake, camel);
2399 }
2400
2401 #[test]
2402 fn test_parse_state_request_unknown_field() {
2403 let body = r#"
2404 {
2405 "contract_ids_with_typo_error": [
2406 {
2407 "address": "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092",
2408 "chain": "ethereum"
2409 }
2410 ],
2411 "version": {
2412 "timestamp": "2069-01-01T04:20:00",
2413 "block": {
2414 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2415 "parentHash": "0x8d75152454e60413efe758cc424bfd339897062d7e658f302765eb7b50971815",
2416 "number": 213,
2417 "chain": "ethereum"
2418 }
2419 }
2420 }
2421 "#;
2422
2423 let decoded = serde_json::from_str::<StateRequestBody>(body);
2424
2425 assert!(decoded.is_err(), "Expected an error due to unknown field");
2426
2427 if let Err(e) = decoded {
2428 assert!(
2429 e.to_string()
2430 .contains("unknown field `contract_ids_with_typo_error`"),
2431 "Error message does not contain expected unknown field information"
2432 );
2433 }
2434 }
2435
2436 #[test]
2437 fn test_parse_state_request_no_contract_specified() {
2438 let json_str = r#"
2439 {
2440 "protocol_system": "uniswap_v2",
2441 "version": {
2442 "timestamp": "2069-01-01T04:20:00",
2443 "block": {
2444 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2445 "number": 213,
2446 "chain": "ethereum"
2447 }
2448 }
2449 }
2450 "#;
2451
2452 let result: StateRequestBody = serde_json::from_str(json_str).unwrap();
2453
2454 let block_hash = "24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4".into();
2455 let block_number = 213;
2456 let expected_timestamp =
2457 NaiveDateTime::parse_from_str("2069-01-01T04:20:00", "%Y-%m-%dT%H:%M:%S").unwrap();
2458
2459 let expected = StateRequestBody {
2460 contract_ids: None,
2461 protocol_system: "uniswap_v2".to_string(),
2462 version: VersionParam {
2463 timestamp: Some(expected_timestamp),
2464 block: Some(BlockParam {
2465 hash: Some(block_hash),
2466 chain: Some(Chain::Ethereum),
2467 number: Some(block_number),
2468 }),
2469 },
2470 chain: Chain::Ethereum,
2471 pagination: PaginationParams { page: 0, page_size: 100 },
2472 };
2473
2474 assert_eq!(result, expected);
2475 }
2476
2477 #[rstest]
2478 #[case::deprecated_ids(
2479 r#"
2480 {
2481 "protocol_ids": [
2482 {
2483 "id": "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092",
2484 "chain": "ethereum"
2485 }
2486 ],
2487 "protocol_system": "uniswap_v2",
2488 "include_balances": false,
2489 "version": {
2490 "timestamp": "2069-01-01T04:20:00",
2491 "block": {
2492 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2493 "number": 213,
2494 "chain": "ethereum"
2495 }
2496 }
2497 }
2498 "#
2499 )]
2500 #[case(
2501 r#"
2502 {
2503 "protocolIds": [
2504 "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092"
2505 ],
2506 "protocol_system": "uniswap_v2",
2507 "include_balances": false,
2508 "version": {
2509 "timestamp": "2069-01-01T04:20:00",
2510 "block": {
2511 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2512 "number": 213,
2513 "chain": "ethereum"
2514 }
2515 }
2516 }
2517 "#
2518 )]
2519 fn test_parse_protocol_state_request(#[case] json_str: &str) {
2520 let result: ProtocolStateRequestBody = serde_json::from_str(json_str).unwrap();
2521
2522 let block_hash = "24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4"
2523 .parse()
2524 .unwrap();
2525 let block_number = 213;
2526
2527 let expected_timestamp =
2528 NaiveDateTime::parse_from_str("2069-01-01T04:20:00", "%Y-%m-%dT%H:%M:%S").unwrap();
2529
2530 let expected = ProtocolStateRequestBody {
2531 protocol_ids: Some(vec!["0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092".to_string()]),
2532 protocol_system: "uniswap_v2".to_string(),
2533 version: VersionParam {
2534 timestamp: Some(expected_timestamp),
2535 block: Some(BlockParam {
2536 hash: Some(block_hash),
2537 chain: Some(Chain::Ethereum),
2538 number: Some(block_number),
2539 }),
2540 },
2541 chain: Chain::Ethereum,
2542 include_balances: false,
2543 pagination: PaginationParams::default(),
2544 };
2545
2546 assert_eq!(result, expected);
2547 }
2548
2549 #[rstest]
2550 #[case::with_protocol_ids(vec![ProtocolId { id: "id1".to_string(), chain: Chain::Ethereum }, ProtocolId { id: "id2".to_string(), chain: Chain::Ethereum }], vec!["id1".to_string(), "id2".to_string()])]
2551 #[case::with_strings(vec!["id1".to_string(), "id2".to_string()], vec!["id1".to_string(), "id2".to_string()])]
2552 fn test_id_filtered<T>(#[case] input_ids: Vec<T>, #[case] expected_ids: Vec<String>)
2553 where
2554 T: Into<String> + Clone,
2555 {
2556 let request_body = ProtocolStateRequestBody::id_filtered(input_ids);
2557
2558 assert_eq!(request_body.protocol_ids, Some(expected_ids));
2559 }
2560
2561 fn create_models_block_changes() -> crate::models::blockchain::BlockAggregatedChanges {
2562 let base_ts = 1694534400; crate::models::blockchain::BlockAggregatedChanges {
2565 extractor: "native_name".to_string(),
2566 block: models::blockchain::Block::new(
2567 3,
2568 models::Chain::Ethereum,
2569 Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000003").unwrap(),
2570 Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000002").unwrap(),
2571 chrono::DateTime::from_timestamp(base_ts + 3000, 0).unwrap().naive_utc(),
2572 ),
2573 db_committed_block_height: Some(1),
2574 finalized_block_height: 1,
2575 revert: true,
2576 state_deltas: HashMap::from([
2577 ("pc_1".to_string(), models::protocol::ProtocolComponentStateDelta {
2578 component_id: "pc_1".to_string(),
2579 updated_attributes: HashMap::from([
2580 ("attr_2".to_string(), Bytes::from("0x0000000000000002")),
2581 ("attr_1".to_string(), Bytes::from("0x00000000000003e8")),
2582 ]),
2583 deleted_attributes: HashSet::new(),
2584 }),
2585 ]),
2586 new_protocol_components: HashMap::from([
2587 ("pc_2".to_string(), crate::models::protocol::ProtocolComponent {
2588 id: "pc_2".to_string(),
2589 protocol_system: "native_protocol_system".to_string(),
2590 protocol_type_name: "pt_1".to_string(),
2591 chain: models::Chain::Ethereum,
2592 tokens: vec![
2593 Bytes::from_str("0xdac17f958d2ee523a2206206994597c13d831ec7").unwrap(),
2594 Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
2595 ],
2596 contract_addresses: vec![],
2597 static_attributes: HashMap::new(),
2598 change: models::ChangeType::Creation,
2599 creation_tx: Bytes::from_str("0x000000000000000000000000000000000000000000000000000000000000c351").unwrap(),
2600 created_at: chrono::DateTime::from_timestamp(base_ts + 5000, 0).unwrap().naive_utc(),
2601 }),
2602 ]),
2603 deleted_protocol_components: HashMap::from([
2604 ("pc_3".to_string(), crate::models::protocol::ProtocolComponent {
2605 id: "pc_3".to_string(),
2606 protocol_system: "native_protocol_system".to_string(),
2607 protocol_type_name: "pt_2".to_string(),
2608 chain: models::Chain::Ethereum,
2609 tokens: vec![
2610 Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(),
2611 Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(),
2612 ],
2613 contract_addresses: vec![],
2614 static_attributes: HashMap::new(),
2615 change: models::ChangeType::Deletion,
2616 creation_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000009c41").unwrap(),
2617 created_at: chrono::DateTime::from_timestamp(base_ts + 4000, 0).unwrap().naive_utc(),
2618 }),
2619 ]),
2620 component_balances: HashMap::from([
2621 ("pc_1".to_string(), HashMap::from([
2622 (Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(), models::protocol::ComponentBalance {
2623 token: Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
2624 balance: Bytes::from("0x00000001"),
2625 balance_float: 1.0,
2626 modify_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
2627 component_id: "pc_1".to_string(),
2628 }),
2629 (Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(), models::protocol::ComponentBalance {
2630 token: Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(),
2631 balance: Bytes::from("0x000003e8"),
2632 balance_float: 1000.0,
2633 modify_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000007531").unwrap(),
2634 component_id: "pc_1".to_string(),
2635 }),
2636 ])),
2637 ]),
2638 account_balances: HashMap::from([
2639 (Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(), HashMap::from([
2640 (Bytes::from_str("0x7a250d5630b4cf539739df2c5dacb4c659f2488d").unwrap(), models::contract::AccountBalance {
2641 account: Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(),
2642 token: Bytes::from_str("0x7a250d5630b4cf539739df2c5dacb4c659f2488d").unwrap(),
2643 balance: Bytes::from("0x000003e8"),
2644 modify_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000007531").unwrap(),
2645 }),
2646 ])),
2647 ]),
2648 ..Default::default()
2649 }
2650 }
2651
2652 #[test]
2653 fn test_serialize_deserialize_block_changes() {
2654 let block_entity_changes = create_models_block_changes();
2659
2660 let json_data = serde_json::to_string(&block_entity_changes).expect("Failed to serialize");
2662
2663 serde_json::from_str::<BlockAggregatedChanges>(&json_data).expect("parsing failed");
2665 }
2666
2667 #[test]
2668 fn test_parse_block_changes() {
2669 let json_data = r#"
2670 {
2671 "extractor": "vm:ambient",
2672 "chain": "ethereum",
2673 "block": {
2674 "number": 123,
2675 "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2676 "parent_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2677 "chain": "ethereum",
2678 "ts": "2023-09-14T00:00:00"
2679 },
2680 "finalized_block_height": 0,
2681 "revert": false,
2682 "new_tokens": {},
2683 "account_updates": {
2684 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2685 "address": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2686 "chain": "ethereum",
2687 "slots": {},
2688 "balance": "0x01f4",
2689 "code": "",
2690 "change": "Update"
2691 }
2692 },
2693 "state_updates": {
2694 "component_1": {
2695 "component_id": "component_1",
2696 "updated_attributes": {"attr1": "0x01"},
2697 "deleted_attributes": ["attr2"]
2698 }
2699 },
2700 "new_protocol_components":
2701 { "protocol_1": {
2702 "id": "protocol_1",
2703 "protocol_system": "system_1",
2704 "protocol_type_name": "type_1",
2705 "chain": "ethereum",
2706 "tokens": ["0x01", "0x02"],
2707 "contract_ids": ["0x01", "0x02"],
2708 "static_attributes": {"attr1": "0x01f4"},
2709 "change": "Update",
2710 "creation_tx": "0x01",
2711 "created_at": "2023-09-14T00:00:00"
2712 }
2713 },
2714 "deleted_protocol_components": {},
2715 "component_balances": {
2716 "protocol_1":
2717 {
2718 "0x01": {
2719 "token": "0x01",
2720 "balance": "0xb77831d23691653a01",
2721 "balance_float": 3.3844151001790677e21,
2722 "modify_tx": "0x01",
2723 "component_id": "protocol_1"
2724 }
2725 }
2726 },
2727 "account_balances": {
2728 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2729 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2730 "account": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2731 "token": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2732 "balance": "0x01f4",
2733 "modify_tx": "0x01"
2734 }
2735 }
2736 },
2737 "component_tvl": {
2738 "protocol_1": 1000.0
2739 },
2740 "dci_update": {
2741 "new_entrypoints": {
2742 "component_1": [
2743 {
2744 "external_id": "0x01:sig()",
2745 "target": "0x01",
2746 "signature": "sig()"
2747 }
2748 ]
2749 },
2750 "new_entrypoint_params": {
2751 "0x01:sig()": [
2752 [
2753 {
2754 "method": "rpctracer",
2755 "caller": "0x01",
2756 "calldata": "0x02"
2757 },
2758 "component_1"
2759 ]
2760 ]
2761 },
2762 "trace_results": {
2763 "0x01:sig()": {
2764 "retriggers": [
2765 ["0x01", {"key": "0x02", "offset": 12}]
2766 ],
2767 "accessed_slots": {
2768 "0x03": ["0x03", "0x04"]
2769 }
2770 }
2771 }
2772 }
2773 }
2774 "#;
2775
2776 serde_json::from_str::<BlockAggregatedChanges>(json_data).expect("parsing failed");
2777 }
2778
2779 #[test]
2780 fn test_parse_websocket_message() {
2781 let json_data = r#"
2782 {
2783 "subscription_id": "5d23bfbe-89ad-4ea3-8672-dc9e973ac9dc",
2784 "deltas": {
2785 "type": "BlockAggregatedChanges",
2786 "extractor": "uniswap_v2",
2787 "chain": "ethereum",
2788 "block": {
2789 "number": 19291517,
2790 "hash": "0xbc3ea4896c0be8da6229387a8571b72818aa258daf4fab46471003ad74c4ee83",
2791 "parent_hash": "0x89ca5b8d593574cf6c886f41ef8208bf6bdc1a90ef36046cb8c84bc880b9af8f",
2792 "chain": "ethereum",
2793 "ts": "2024-02-23T16:35:35"
2794 },
2795 "finalized_block_height": 0,
2796 "revert": false,
2797 "new_tokens": {},
2798 "account_updates": {
2799 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2800 "address": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2801 "chain": "ethereum",
2802 "slots": {},
2803 "balance": "0x01f4",
2804 "code": "",
2805 "change": "Update"
2806 }
2807 },
2808 "state_updates": {
2809 "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28": {
2810 "component_id": "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28",
2811 "updated_attributes": {
2812 "reserve0": "0x87f7b5973a7f28a8b32404",
2813 "reserve1": "0x09e9564b11"
2814 },
2815 "deleted_attributes": []
2816 },
2817 "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d": {
2818 "component_id": "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d",
2819 "updated_attributes": {
2820 "reserve1": "0x44d9a8fd662c2f4d03",
2821 "reserve0": "0x500b1261f811d5bf423e"
2822 },
2823 "deleted_attributes": []
2824 }
2825 },
2826 "new_protocol_components": {},
2827 "deleted_protocol_components": {},
2828 "component_balances": {
2829 "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d": {
2830 "0x9012744b7a564623b6c3e40b144fc196bdedf1a9": {
2831 "token": "0x9012744b7a564623b6c3e40b144fc196bdedf1a9",
2832 "balance": "0x500b1261f811d5bf423e",
2833 "balance_float": 3.779935574269033E23,
2834 "modify_tx": "0xe46c4db085fb6c6f3408a65524555797adb264e1d5cf3b66ad154598f85ac4bf",
2835 "component_id": "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d"
2836 },
2837 "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": {
2838 "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
2839 "balance": "0x44d9a8fd662c2f4d03",
2840 "balance_float": 1.270062661329837E21,
2841 "modify_tx": "0xe46c4db085fb6c6f3408a65524555797adb264e1d5cf3b66ad154598f85ac4bf",
2842 "component_id": "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d"
2843 }
2844 }
2845 },
2846 "account_balances": {
2847 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2848 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2849 "account": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2850 "token": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2851 "balance": "0x01f4",
2852 "modify_tx": "0x01"
2853 }
2854 }
2855 },
2856 "component_tvl": {},
2857 "dci_update": {
2858 "new_entrypoints": {
2859 "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28": [
2860 {
2861 "external_id": "0x01:sig()",
2862 "target": "0x01",
2863 "signature": "sig()"
2864 }
2865 ]
2866 },
2867 "new_entrypoint_params": {
2868 "0x01:sig()": [
2869 [
2870 {
2871 "method": "rpctracer",
2872 "caller": "0x01",
2873 "calldata": "0x02"
2874 },
2875 "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28"
2876 ]
2877 ]
2878 },
2879 "trace_results": {
2880 "0x01:sig()": {
2881 "retriggers": [
2882 ["0x01", {"key": "0x02", "offset": 12}]
2883 ],
2884 "accessed_slots": {
2885 "0x03": ["0x03", "0x04"]
2886 }
2887 }
2888 }
2889 }
2890 }
2891 }
2892 "#;
2893 serde_json::from_str::<WebSocketMessage>(json_data).expect("parsing failed");
2894 }
2895
2896 #[test]
2897 fn test_protocol_state_delta_merge_update_delete() {
2898 let mut delta1 = ProtocolStateDelta {
2900 component_id: "Component1".to_string(),
2901 updated_attributes: HashMap::from([(
2902 "Attribute1".to_string(),
2903 Bytes::from("0xbadbabe420"),
2904 )]),
2905 deleted_attributes: HashSet::new(),
2906 };
2907 let delta2 = ProtocolStateDelta {
2908 component_id: "Component1".to_string(),
2909 updated_attributes: HashMap::from([(
2910 "Attribute2".to_string(),
2911 Bytes::from("0x0badbabe"),
2912 )]),
2913 deleted_attributes: HashSet::from(["Attribute1".to_string()]),
2914 };
2915 let exp = ProtocolStateDelta {
2916 component_id: "Component1".to_string(),
2917 updated_attributes: HashMap::from([(
2918 "Attribute2".to_string(),
2919 Bytes::from("0x0badbabe"),
2920 )]),
2921 deleted_attributes: HashSet::from(["Attribute1".to_string()]),
2922 };
2923
2924 delta1.merge(&delta2);
2925
2926 assert_eq!(delta1, exp);
2927 }
2928
2929 #[test]
2930 fn test_protocol_state_delta_merge_delete_update() {
2931 let mut delta1 = ProtocolStateDelta {
2933 component_id: "Component1".to_string(),
2934 updated_attributes: HashMap::new(),
2935 deleted_attributes: HashSet::from(["Attribute1".to_string()]),
2936 };
2937 let delta2 = ProtocolStateDelta {
2938 component_id: "Component1".to_string(),
2939 updated_attributes: HashMap::from([(
2940 "Attribute1".to_string(),
2941 Bytes::from("0x0badbabe"),
2942 )]),
2943 deleted_attributes: HashSet::new(),
2944 };
2945 let exp = ProtocolStateDelta {
2946 component_id: "Component1".to_string(),
2947 updated_attributes: HashMap::from([(
2948 "Attribute1".to_string(),
2949 Bytes::from("0x0badbabe"),
2950 )]),
2951 deleted_attributes: HashSet::new(),
2952 };
2953
2954 delta1.merge(&delta2);
2955
2956 assert_eq!(delta1, exp);
2957 }
2958
2959 #[test]
2960 fn test_account_update_merge() {
2961 let mut account1 = AccountUpdate::new(
2963 Bytes::from(b"0x1234"),
2964 Chain::Ethereum,
2965 HashMap::from([(Bytes::from("0xaabb"), Bytes::from("0xccdd"))]),
2966 Some(Bytes::from("0x1000")),
2967 Some(Bytes::from("0xdeadbeaf")),
2968 ChangeType::Creation,
2969 );
2970
2971 let account2 = AccountUpdate::new(
2972 Bytes::from(b"0x1234"), Chain::Ethereum,
2974 HashMap::from([(Bytes::from("0xeeff"), Bytes::from("0x11223344"))]),
2975 Some(Bytes::from("0x2000")),
2976 Some(Bytes::from("0xcafebabe")),
2977 ChangeType::Update,
2978 );
2979
2980 account1.merge(&account2);
2982
2983 let expected = AccountUpdate::new(
2985 Bytes::from(b"0x1234"), Chain::Ethereum,
2987 HashMap::from([
2988 (Bytes::from("0xaabb"), Bytes::from("0xccdd")), (Bytes::from("0xeeff"), Bytes::from("0x11223344")), ]),
2991 Some(Bytes::from("0x2000")), Some(Bytes::from("0xcafebabe")), ChangeType::Creation, );
2995
2996 assert_eq!(account1, expected);
2998 }
2999
3000 #[test]
3001 fn test_block_account_changes_merge() {
3002 let old_account_updates: HashMap<Bytes, AccountUpdate> = [(
3004 Bytes::from("0x0011"),
3005 AccountUpdate {
3006 address: Bytes::from("0x00"),
3007 chain: Chain::Ethereum,
3008 slots: HashMap::from([(Bytes::from("0x0022"), Bytes::from("0x0033"))]),
3009 balance: Some(Bytes::from("0x01")),
3010 code: Some(Bytes::from("0x02")),
3011 change: ChangeType::Creation,
3012 },
3013 )]
3014 .into_iter()
3015 .collect();
3016 let new_account_updates: HashMap<Bytes, AccountUpdate> = [(
3017 Bytes::from("0x0011"),
3018 AccountUpdate {
3019 address: Bytes::from("0x00"),
3020 chain: Chain::Ethereum,
3021 slots: HashMap::from([(Bytes::from("0x0044"), Bytes::from("0x0055"))]),
3022 balance: Some(Bytes::from("0x03")),
3023 code: Some(Bytes::from("0x04")),
3024 change: ChangeType::Update,
3025 },
3026 )]
3027 .into_iter()
3028 .collect();
3029 let block_account_changes_initial = BlockAggregatedChanges {
3031 extractor: "extractor1".to_string(),
3032 revert: false,
3033 account_updates: old_account_updates,
3034 ..Default::default()
3035 };
3036
3037 let block_account_changes_new = BlockAggregatedChanges {
3038 extractor: "extractor2".to_string(),
3039 revert: true,
3040 account_updates: new_account_updates,
3041 ..Default::default()
3042 };
3043
3044 let res = block_account_changes_initial.merge(block_account_changes_new);
3046
3047 let expected_account_updates: HashMap<Bytes, AccountUpdate> = [(
3049 Bytes::from("0x0011"),
3050 AccountUpdate {
3051 address: Bytes::from("0x00"),
3052 chain: Chain::Ethereum,
3053 slots: HashMap::from([
3054 (Bytes::from("0x0044"), Bytes::from("0x0055")),
3055 (Bytes::from("0x0022"), Bytes::from("0x0033")),
3056 ]),
3057 balance: Some(Bytes::from("0x03")),
3058 code: Some(Bytes::from("0x04")),
3059 change: ChangeType::Creation,
3060 },
3061 )]
3062 .into_iter()
3063 .collect();
3064 let block_account_changes_expected = BlockAggregatedChanges {
3065 extractor: "extractor1".to_string(),
3066 revert: true,
3067 account_updates: expected_account_updates,
3068 ..Default::default()
3069 };
3070 assert_eq!(res, block_account_changes_expected);
3071 }
3072
3073 #[test]
3074 fn test_block_entity_changes_merge() {
3075 let block_entity_changes_result1 = BlockAggregatedChanges {
3077 extractor: String::from("extractor1"),
3078 revert: false,
3079 state_updates: hashmap! { "state1".to_string() => ProtocolStateDelta::default() },
3080 new_protocol_components: hashmap! { "component1".to_string() => ProtocolComponent::default() },
3081 deleted_protocol_components: HashMap::new(),
3082 component_balances: hashmap! {
3083 "component1".to_string() => TokenBalances(hashmap! {
3084 Bytes::from("0x01") => ComponentBalance {
3085 token: Bytes::from("0x01"),
3086 balance: Bytes::from("0x01"),
3087 balance_float: 1.0,
3088 modify_tx: Bytes::from("0x00"),
3089 component_id: "component1".to_string()
3090 },
3091 Bytes::from("0x02") => ComponentBalance {
3092 token: Bytes::from("0x02"),
3093 balance: Bytes::from("0x02"),
3094 balance_float: 2.0,
3095 modify_tx: Bytes::from("0x00"),
3096 component_id: "component1".to_string()
3097 },
3098 })
3099
3100 },
3101 component_tvl: hashmap! { "tvl1".to_string() => 1000.0 },
3102 ..Default::default()
3103 };
3104 let block_entity_changes_result2 = BlockAggregatedChanges {
3105 extractor: String::from("extractor2"),
3106 revert: true,
3107 state_updates: hashmap! { "state2".to_string() => ProtocolStateDelta::default() },
3108 new_protocol_components: hashmap! { "component2".to_string() => ProtocolComponent::default() },
3109 deleted_protocol_components: hashmap! { "component3".to_string() => ProtocolComponent::default() },
3110 component_balances: hashmap! {
3111 "component1".to_string() => TokenBalances::default(),
3112 "component2".to_string() => TokenBalances::default()
3113 },
3114 component_tvl: hashmap! { "tvl2".to_string() => 2000.0 },
3115 ..Default::default()
3116 };
3117
3118 let res = block_entity_changes_result1.merge(block_entity_changes_result2);
3119
3120 let expected_block_entity_changes_result = BlockAggregatedChanges {
3121 extractor: String::from("extractor1"),
3122 revert: true,
3123 state_updates: hashmap! {
3124 "state1".to_string() => ProtocolStateDelta::default(),
3125 "state2".to_string() => ProtocolStateDelta::default(),
3126 },
3127 new_protocol_components: hashmap! {
3128 "component1".to_string() => ProtocolComponent::default(),
3129 "component2".to_string() => ProtocolComponent::default(),
3130 },
3131 deleted_protocol_components: hashmap! {
3132 "component3".to_string() => ProtocolComponent::default(),
3133 },
3134 component_balances: hashmap! {
3135 "component1".to_string() => TokenBalances(hashmap! {
3136 Bytes::from("0x01") => ComponentBalance {
3137 token: Bytes::from("0x01"),
3138 balance: Bytes::from("0x01"),
3139 balance_float: 1.0,
3140 modify_tx: Bytes::from("0x00"),
3141 component_id: "component1".to_string()
3142 },
3143 Bytes::from("0x02") => ComponentBalance {
3144 token: Bytes::from("0x02"),
3145 balance: Bytes::from("0x02"),
3146 balance_float: 2.0,
3147 modify_tx: Bytes::from("0x00"),
3148 component_id: "component1".to_string()
3149 },
3150 }),
3151 "component2".to_string() => TokenBalances::default(),
3152 },
3153 component_tvl: hashmap! {
3154 "tvl1".to_string() => 1000.0,
3155 "tvl2".to_string() => 2000.0
3156 },
3157 ..Default::default()
3158 };
3159
3160 assert_eq!(res, expected_block_entity_changes_result);
3161 }
3162
3163 #[test]
3164 fn test_websocket_error_serialization() {
3165 let extractor_id = ExtractorIdentity::new(Chain::Ethereum, "test_extractor");
3166 let subscription_id = Uuid::new_v4();
3167
3168 let error = WebsocketError::ExtractorNotFound(extractor_id.clone());
3170 let json = serde_json::to_string(&error).unwrap();
3171 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
3172 assert_eq!(error, deserialized);
3173
3174 let error = WebsocketError::SubscriptionNotFound(subscription_id);
3176 let json = serde_json::to_string(&error).unwrap();
3177 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
3178 assert_eq!(error, deserialized);
3179
3180 let error = WebsocketError::ParseError("{asd".to_string(), "invalid json".to_string());
3182 let json = serde_json::to_string(&error).unwrap();
3183 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
3184 assert_eq!(error, deserialized);
3185
3186 let error = WebsocketError::SubscribeError(extractor_id.clone());
3188 let json = serde_json::to_string(&error).unwrap();
3189 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
3190 assert_eq!(error, deserialized);
3191
3192 let error =
3194 WebsocketError::CompressionError(subscription_id, "Compression failed".to_string());
3195 let json = serde_json::to_string(&error).unwrap();
3196 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
3197 assert_eq!(error, deserialized);
3198 }
3199
3200 #[test]
3201 fn test_websocket_message_with_error_response() {
3202 let error =
3203 WebsocketError::ParseError("}asdfas".to_string(), "malformed request".to_string());
3204 let response = Response::Error(error.clone());
3205 let message = WebSocketMessage::Response(response);
3206
3207 let json = serde_json::to_string(&message).unwrap();
3208 let deserialized: WebSocketMessage = serde_json::from_str(&json).unwrap();
3209
3210 if let WebSocketMessage::Response(Response::Error(deserialized_error)) = deserialized {
3211 assert_eq!(error, deserialized_error);
3212 } else {
3213 panic!("Expected WebSocketMessage::Response(Response::Error)");
3214 }
3215 }
3216
3217 #[test]
3218 fn test_websocket_error_conversion_from_models() {
3219 use crate::models::error;
3220
3221 let extractor_id =
3222 crate::models::ExtractorIdentity::new(crate::models::Chain::Ethereum, "test");
3223 let subscription_id = Uuid::new_v4();
3224
3225 let models_error = error::WebsocketError::ExtractorNotFound(extractor_id.clone());
3227 let dto_error: WebsocketError = models_error.into();
3228 assert_eq!(dto_error, WebsocketError::ExtractorNotFound(extractor_id.clone().into()));
3229
3230 let models_error = error::WebsocketError::SubscriptionNotFound(subscription_id);
3232 let dto_error: WebsocketError = models_error.into();
3233 assert_eq!(dto_error, WebsocketError::SubscriptionNotFound(subscription_id));
3234
3235 let json_result: Result<serde_json::Value, _> = serde_json::from_str("{invalid json");
3237 let json_error = json_result.unwrap_err();
3238 let models_error =
3239 error::WebsocketError::ParseError("{invalid json".to_string(), json_error);
3240 let dto_error: WebsocketError = models_error.into();
3241 if let WebsocketError::ParseError(msg, error_msg) = dto_error {
3242 assert!(!error_msg.is_empty(), "Error message should not be empty, got: '{}'", msg);
3244 } else {
3245 panic!("Expected ParseError variant");
3246 }
3247
3248 let models_error = error::WebsocketError::SubscribeError(extractor_id.clone());
3250 let dto_error: WebsocketError = models_error.into();
3251 assert_eq!(dto_error, WebsocketError::SubscribeError(extractor_id.into()));
3252
3253 let io_error = std::io::Error::other("Compression failed");
3255 let models_error = error::WebsocketError::CompressionError(subscription_id, io_error);
3256 let dto_error: WebsocketError = models_error.into();
3257 if let WebsocketError::CompressionError(sub_id, msg) = &dto_error {
3258 assert_eq!(*sub_id, subscription_id);
3259 assert!(msg.contains("Compression failed"));
3260 } else {
3261 panic!("Expected CompressionError variant");
3262 }
3263 }
3264}
3265
3266#[cfg(test)]
3267mod memory_size_tests {
3268 use std::collections::HashMap;
3269
3270 use super::*;
3271
3272 #[test]
3273 fn test_state_request_response_memory_size_empty() {
3274 let response = StateRequestResponse {
3275 accounts: vec![],
3276 pagination: PaginationResponse::new(1, 10, 0),
3277 };
3278
3279 let size = response.deep_size_of();
3280
3281 assert!(size >= 48, "Empty response should have minimum size of 48 bytes, got {}", size);
3283 assert!(size < 200, "Empty response should not be too large, got {}", size);
3284 }
3285
3286 #[test]
3287 fn test_state_request_response_memory_size_scales_with_slots() {
3288 let create_response_with_slots = |slot_count: usize| {
3289 let mut slots = HashMap::new();
3290 for i in 0..slot_count {
3291 let key = vec![i as u8; 32]; let value = vec![(i + 100) as u8; 32]; slots.insert(key.into(), value.into());
3294 }
3295
3296 let account = ResponseAccount::new(
3297 Chain::Ethereum,
3298 vec![1; 20].into(),
3299 "Pool".to_string(),
3300 slots,
3301 vec![1; 32].into(),
3302 HashMap::new(),
3303 vec![].into(), vec![1; 32].into(),
3305 vec![1; 32].into(),
3306 vec![1; 32].into(),
3307 None,
3308 );
3309
3310 StateRequestResponse {
3311 accounts: vec![account],
3312 pagination: PaginationResponse::new(1, 10, 1),
3313 }
3314 };
3315
3316 let small_response = create_response_with_slots(10);
3317 let large_response = create_response_with_slots(100);
3318
3319 let small_size = small_response.deep_size_of();
3320 let large_size = large_response.deep_size_of();
3321
3322 assert!(
3324 large_size > small_size * 5,
3325 "Large response ({} bytes) should be much larger than small response ({} bytes)",
3326 large_size,
3327 small_size
3328 );
3329
3330 let size_diff = large_size - small_size;
3332 let expected_min_diff = 90 * 64; assert!(
3334 size_diff > expected_min_diff,
3335 "Size difference ({} bytes) should reflect the additional slot data",
3336 size_diff
3337 );
3338 }
3339}
3340
3341#[cfg(test)]
3342mod pagination_limits_tests {
3343 use super::*;
3344
3345 #[derive(Clone, Debug)]
3347 struct TestRequestBody {
3348 pagination: PaginationParams,
3349 }
3350
3351 impl_pagination_limits!(TestRequestBody, compressed = 500, uncompressed = 50);
3353
3354 #[test]
3355 fn test_effective_max_page_size() {
3356 let max_size = TestRequestBody::effective_max_page_size(true);
3358 assert_eq!(max_size, 500, "Should return compressed limit when compression is enabled");
3359
3360 let max_size = TestRequestBody::effective_max_page_size(false);
3362 assert_eq!(max_size, 50, "Should return uncompressed limit when compression is disabled");
3363 }
3364}