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