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(
770 Clone, Serialize, Debug, Default, Deserialize, PartialEq, ToSchema, Eq, Hash, DeepSizeOf,
771)]
772#[serde(deny_unknown_fields)]
773pub struct StateRequestBody {
774 #[serde(alias = "contractIds")]
776 #[schema(value_type=Option<Vec<String>>)]
777 pub contract_ids: Option<Vec<Bytes>>,
778 #[serde(alias = "protocolSystem", default)]
781 pub protocol_system: String,
782 #[serde(default = "VersionParam::default")]
783 pub version: VersionParam,
784 #[serde(default)]
785 pub chain: Chain,
786 #[serde(default)]
787 pub pagination: PaginationParams,
788}
789
790impl StateRequestBody {
791 pub fn new(
792 contract_ids: Option<Vec<Bytes>>,
793 protocol_system: String,
794 version: VersionParam,
795 chain: Chain,
796 pagination: PaginationParams,
797 ) -> Self {
798 Self { contract_ids, protocol_system, version, chain, pagination }
799 }
800
801 pub fn from_block(protocol_system: &str, block: BlockParam) -> Self {
802 Self {
803 contract_ids: None,
804 protocol_system: protocol_system.to_string(),
805 version: VersionParam { timestamp: None, block: Some(block.clone()) },
806 chain: block.chain.unwrap_or_default(),
807 pagination: PaginationParams::default(),
808 }
809 }
810
811 pub fn from_timestamp(protocol_system: &str, timestamp: NaiveDateTime, chain: Chain) -> Self {
812 Self {
813 contract_ids: None,
814 protocol_system: protocol_system.to_string(),
815 version: VersionParam { timestamp: Some(timestamp), block: None },
816 chain,
817 pagination: PaginationParams::default(),
818 }
819 }
820}
821
822#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, DeepSizeOf)]
824pub struct StateRequestResponse {
825 pub accounts: Vec<ResponseAccount>,
826 pub pagination: PaginationResponse,
827}
828
829impl StateRequestResponse {
830 pub fn new(accounts: Vec<ResponseAccount>, pagination: PaginationResponse) -> Self {
831 Self { accounts, pagination }
832 }
833}
834
835#[derive(PartialEq, Clone, Serialize, Deserialize, Default, ToSchema, DeepSizeOf)]
836#[serde(rename = "Account")]
837pub struct ResponseAccount {
841 pub chain: Chain,
842 #[schema(value_type=String, example="0xc9f2e6ea1637E499406986ac50ddC92401ce1f58")]
844 #[serde(with = "hex_bytes")]
845 pub address: Bytes,
846 #[schema(value_type=String, example="Protocol Vault")]
848 pub title: String,
849 #[schema(value_type=HashMap<String, String>, example=json!({"0x....": "0x...."}))]
851 #[serde(with = "hex_hashmap_key_value")]
852 pub slots: HashMap<Bytes, Bytes>,
853 #[schema(value_type=String, example="0x00")]
855 #[serde(with = "hex_bytes")]
856 pub native_balance: Bytes,
857 #[schema(value_type=HashMap<String, String>, example=json!({"0x....": "0x...."}))]
860 #[serde(with = "hex_hashmap_key_value")]
861 pub token_balances: HashMap<Bytes, Bytes>,
862 #[schema(value_type=String, example="0xBADBABE")]
864 #[serde(with = "hex_bytes")]
865 pub code: Bytes,
866 #[schema(value_type=String, example="0x123456789")]
868 #[serde(with = "hex_bytes")]
869 pub code_hash: Bytes,
870 #[schema(value_type=String, example="0x8f1133bfb054a23aedfe5d25b1d81b96195396d8b88bd5d4bcf865fc1ae2c3f4")]
872 #[serde(with = "hex_bytes")]
873 pub balance_modify_tx: Bytes,
874 #[schema(value_type=String, example="0x8f1133bfb054a23aedfe5d25b1d81b96195396d8b88bd5d4bcf865fc1ae2c3f4")]
876 #[serde(with = "hex_bytes")]
877 pub code_modify_tx: Bytes,
878 #[deprecated(note = "The `creation_tx` field is deprecated.")]
880 #[schema(value_type=Option<String>, example="0x8f1133bfb054a23aedfe5d25b1d81b96195396d8b88bd5d4bcf865fc1ae2c3f4")]
881 #[serde(with = "hex_bytes_option")]
882 pub creation_tx: Option<Bytes>,
883}
884
885impl ResponseAccount {
886 #[allow(clippy::too_many_arguments)]
887 pub fn new(
888 chain: Chain,
889 address: Bytes,
890 title: String,
891 slots: HashMap<Bytes, Bytes>,
892 native_balance: Bytes,
893 token_balances: HashMap<Bytes, Bytes>,
894 code: Bytes,
895 code_hash: Bytes,
896 balance_modify_tx: Bytes,
897 code_modify_tx: Bytes,
898 creation_tx: Option<Bytes>,
899 ) -> Self {
900 Self {
901 chain,
902 address,
903 title,
904 slots,
905 native_balance,
906 token_balances,
907 code,
908 code_hash,
909 balance_modify_tx,
910 code_modify_tx,
911 creation_tx,
912 }
913 }
914}
915
916impl fmt::Debug for ResponseAccount {
918 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
919 f.debug_struct("ResponseAccount")
920 .field("chain", &self.chain)
921 .field("address", &self.address)
922 .field("title", &self.title)
923 .field("slots", &self.slots)
924 .field("native_balance", &self.native_balance)
925 .field("token_balances", &self.token_balances)
926 .field("code", &format!("[{} bytes]", self.code.len()))
927 .field("code_hash", &self.code_hash)
928 .field("balance_modify_tx", &self.balance_modify_tx)
929 .field("code_modify_tx", &self.code_modify_tx)
930 .field("creation_tx", &self.creation_tx)
931 .finish()
932 }
933}
934
935#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
936pub struct AccountBalance {
937 #[serde(with = "hex_bytes")]
938 pub account: Bytes,
939 #[serde(with = "hex_bytes")]
940 pub token: Bytes,
941 #[serde(with = "hex_bytes")]
942 pub balance: Bytes,
943 #[serde(with = "hex_bytes")]
944 pub modify_tx: Bytes,
945}
946
947#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
948#[serde(deny_unknown_fields)]
949pub struct ContractId {
950 #[serde(with = "hex_bytes")]
951 #[schema(value_type=String)]
952 pub address: Bytes,
953 pub chain: Chain,
954}
955
956impl ContractId {
958 pub fn new(chain: Chain, address: Bytes) -> Self {
959 Self { address, chain }
960 }
961
962 pub fn address(&self) -> &Bytes {
963 &self.address
964 }
965}
966
967impl fmt::Display for ContractId {
968 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
969 write!(f, "{:?}: 0x{}", self.chain, hex::encode(&self.address))
970 }
971}
972
973#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema, Eq, Hash)]
980#[serde(deny_unknown_fields)]
981pub struct VersionParam {
982 pub timestamp: Option<NaiveDateTime>,
983 pub block: Option<BlockParam>,
984}
985
986impl DeepSizeOf for VersionParam {
987 fn deep_size_of_children(&self, ctx: &mut Context) -> usize {
988 if let Some(block) = &self.block {
989 return block.deep_size_of_children(ctx);
990 }
991
992 0
993 }
994}
995
996impl VersionParam {
997 pub fn new(timestamp: Option<NaiveDateTime>, block: Option<BlockParam>) -> Self {
998 Self { timestamp, block }
999 }
1000}
1001
1002impl Default for VersionParam {
1003 fn default() -> Self {
1004 VersionParam { timestamp: Some(Utc::now().naive_utc()), block: None }
1005 }
1006}
1007
1008#[deprecated(note = "Use StateRequestBody instead")]
1009#[derive(Serialize, Deserialize, Default, Debug, IntoParams)]
1010pub struct StateRequestParameters {
1011 #[param(default = 0)]
1013 pub tvl_gt: Option<u64>,
1014 #[param(default = 0)]
1016 pub inertia_min_gt: Option<u64>,
1017 #[serde(default = "default_include_balances_flag")]
1019 pub include_balances: bool,
1020 #[serde(default)]
1021 pub pagination: PaginationParams,
1022}
1023
1024impl StateRequestParameters {
1025 pub fn new(include_balances: bool) -> Self {
1026 Self {
1027 tvl_gt: None,
1028 inertia_min_gt: None,
1029 include_balances,
1030 pagination: PaginationParams::default(),
1031 }
1032 }
1033
1034 pub fn to_query_string(&self) -> String {
1035 let mut parts = vec![format!("include_balances={}", self.include_balances)];
1036
1037 if let Some(tvl_gt) = self.tvl_gt {
1038 parts.push(format!("tvl_gt={tvl_gt}"));
1039 }
1040
1041 if let Some(inertia) = self.inertia_min_gt {
1042 parts.push(format!("inertia_min_gt={inertia}"));
1043 }
1044
1045 let mut res = parts.join("&");
1046 if !res.is_empty() {
1047 res = format!("?{res}");
1048 }
1049 res
1050 }
1051}
1052
1053#[derive(
1054 Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Hash, Clone, DeepSizeOf,
1055)]
1056#[serde(deny_unknown_fields)]
1057pub struct TokensRequestBody {
1058 #[serde(alias = "tokenAddresses")]
1060 #[schema(value_type=Option<Vec<String>>)]
1061 pub token_addresses: Option<Vec<Bytes>>,
1062 #[serde(default)]
1070 pub min_quality: Option<i32>,
1071 #[serde(default)]
1073 pub traded_n_days_ago: Option<u64>,
1074 #[serde(default)]
1076 pub pagination: PaginationParams,
1077 #[serde(default)]
1079 pub chain: Chain,
1080}
1081
1082#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, Eq, Hash, DeepSizeOf)]
1084pub struct TokensRequestResponse {
1085 pub tokens: Vec<ResponseToken>,
1086 pub pagination: PaginationResponse,
1087}
1088
1089impl TokensRequestResponse {
1090 pub fn new(tokens: Vec<ResponseToken>, pagination_request: &PaginationResponse) -> Self {
1091 Self { tokens, pagination: pagination_request.clone() }
1092 }
1093}
1094
1095#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, Eq, Hash, DeepSizeOf)]
1097#[serde(deny_unknown_fields)]
1098pub struct PaginationParams {
1099 #[serde(default)]
1101 pub page: i64,
1102 #[serde(default)]
1104 #[schema(default = 10)]
1105 pub page_size: i64,
1106}
1107
1108impl PaginationParams {
1109 pub fn new(page: i64, page_size: i64) -> Self {
1110 Self { page, page_size }
1111 }
1112}
1113
1114impl Default for PaginationParams {
1115 fn default() -> Self {
1116 PaginationParams { page: 0, page_size: 20 }
1117 }
1118}
1119
1120#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, Eq, Hash, DeepSizeOf)]
1121#[serde(deny_unknown_fields)]
1122pub struct PaginationResponse {
1123 pub page: i64,
1124 pub page_size: i64,
1125 pub total: i64,
1127}
1128
1129impl PaginationResponse {
1131 pub fn new(page: i64, page_size: i64, total: i64) -> Self {
1132 Self { page, page_size, total }
1133 }
1134
1135 pub fn total_pages(&self) -> i64 {
1136 (self.total + self.page_size - 1) / self.page_size
1138 }
1139}
1140
1141#[derive(
1142 PartialEq, Debug, Clone, Serialize, Deserialize, Default, ToSchema, Eq, Hash, DeepSizeOf,
1143)]
1144#[serde(rename = "Token")]
1145pub struct ResponseToken {
1147 pub chain: Chain,
1148 #[schema(value_type=String, example="0xc9f2e6ea1637E499406986ac50ddC92401ce1f58")]
1150 #[serde(with = "hex_bytes")]
1151 pub address: Bytes,
1152 #[schema(value_type=String, example="WETH")]
1154 pub symbol: String,
1155 pub decimals: u32,
1157 pub tax: u64,
1159 pub gas: Vec<Option<u64>>,
1161 pub quality: u32,
1169}
1170
1171impl From<models::token::Token> for ResponseToken {
1172 fn from(value: models::token::Token) -> Self {
1173 Self {
1174 chain: value.chain.into(),
1175 address: value.address,
1176 symbol: value.symbol,
1177 decimals: value.decimals,
1178 tax: value.tax,
1179 gas: value.gas,
1180 quality: value.quality,
1181 }
1182 }
1183}
1184
1185#[derive(Serialize, Deserialize, Debug, Default, ToSchema, Clone, DeepSizeOf)]
1186#[serde(deny_unknown_fields)]
1187pub struct ProtocolComponentsRequestBody {
1188 pub protocol_system: String,
1191 #[schema(value_type=Option<Vec<String>>)]
1193 #[serde(alias = "componentAddresses")]
1194 pub component_ids: Option<Vec<ComponentId>>,
1195 #[serde(default)]
1198 pub tvl_gt: Option<f64>,
1199 #[serde(default)]
1200 pub chain: Chain,
1201 #[serde(default)]
1203 pub pagination: PaginationParams,
1204}
1205
1206impl PartialEq for ProtocolComponentsRequestBody {
1208 fn eq(&self, other: &Self) -> bool {
1209 let tvl_close_enough = match (self.tvl_gt, other.tvl_gt) {
1210 (Some(a), Some(b)) => (a - b).abs() < 1e-6,
1211 (None, None) => true,
1212 _ => false,
1213 };
1214
1215 self.protocol_system == other.protocol_system &&
1216 self.component_ids == other.component_ids &&
1217 tvl_close_enough &&
1218 self.chain == other.chain &&
1219 self.pagination == other.pagination
1220 }
1221}
1222
1223impl Eq for ProtocolComponentsRequestBody {}
1225
1226impl Hash for ProtocolComponentsRequestBody {
1227 fn hash<H: Hasher>(&self, state: &mut H) {
1228 self.protocol_system.hash(state);
1229 self.component_ids.hash(state);
1230
1231 if let Some(tvl) = self.tvl_gt {
1233 tvl.to_bits().hash(state);
1235 } else {
1236 state.write_u8(0);
1238 }
1239
1240 self.chain.hash(state);
1241 self.pagination.hash(state);
1242 }
1243}
1244
1245impl ProtocolComponentsRequestBody {
1246 pub fn system_filtered(system: &str, tvl_gt: Option<f64>, chain: Chain) -> Self {
1247 Self {
1248 protocol_system: system.to_string(),
1249 component_ids: None,
1250 tvl_gt,
1251 chain,
1252 pagination: Default::default(),
1253 }
1254 }
1255
1256 pub fn id_filtered(system: &str, ids: Vec<String>, chain: Chain) -> Self {
1257 Self {
1258 protocol_system: system.to_string(),
1259 component_ids: Some(ids),
1260 tvl_gt: None,
1261 chain,
1262 pagination: Default::default(),
1263 }
1264 }
1265}
1266
1267impl ProtocolComponentsRequestBody {
1268 pub fn new(
1269 protocol_system: String,
1270 component_ids: Option<Vec<String>>,
1271 tvl_gt: Option<f64>,
1272 chain: Chain,
1273 pagination: PaginationParams,
1274 ) -> Self {
1275 Self { protocol_system, component_ids, tvl_gt, chain, pagination }
1276 }
1277}
1278
1279#[deprecated(note = "Use ProtocolComponentsRequestBody instead")]
1280#[derive(Serialize, Deserialize, Default, Debug, IntoParams)]
1281pub struct ProtocolComponentRequestParameters {
1282 #[param(default = 0)]
1284 pub tvl_gt: Option<f64>,
1285}
1286
1287impl ProtocolComponentRequestParameters {
1288 pub fn tvl_filtered(min_tvl: f64) -> Self {
1289 Self { tvl_gt: Some(min_tvl) }
1290 }
1291}
1292
1293impl ProtocolComponentRequestParameters {
1294 pub fn to_query_string(&self) -> String {
1295 if let Some(tvl_gt) = self.tvl_gt {
1296 return format!("?tvl_gt={tvl_gt}");
1297 }
1298 String::new()
1299 }
1300}
1301
1302#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, DeepSizeOf)]
1304pub struct ProtocolComponentRequestResponse {
1305 pub protocol_components: Vec<ProtocolComponent>,
1306 pub pagination: PaginationResponse,
1307}
1308
1309impl ProtocolComponentRequestResponse {
1310 pub fn new(
1311 protocol_components: Vec<ProtocolComponent>,
1312 pagination: PaginationResponse,
1313 ) -> Self {
1314 Self { protocol_components, pagination }
1315 }
1316}
1317
1318#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema, Eq, Hash)]
1319#[serde(deny_unknown_fields)]
1320#[deprecated]
1321pub struct ProtocolId {
1322 pub id: String,
1323 pub chain: Chain,
1324}
1325
1326impl From<ProtocolId> for String {
1327 fn from(protocol_id: ProtocolId) -> Self {
1328 protocol_id.id
1329 }
1330}
1331
1332impl AsRef<str> for ProtocolId {
1333 fn as_ref(&self) -> &str {
1334 &self.id
1335 }
1336}
1337
1338#[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize, ToSchema, DeepSizeOf)]
1340pub struct ResponseProtocolState {
1341 pub component_id: String,
1343 #[schema(value_type=HashMap<String, String>)]
1346 #[serde(with = "hex_hashmap_value")]
1347 pub attributes: HashMap<String, Bytes>,
1348 #[schema(value_type=HashMap<String, String>)]
1350 #[serde(with = "hex_hashmap_key_value")]
1351 pub balances: HashMap<Bytes, Bytes>,
1352}
1353
1354impl From<models::protocol::ProtocolComponentState> for ResponseProtocolState {
1355 fn from(value: models::protocol::ProtocolComponentState) -> Self {
1356 Self {
1357 component_id: value.component_id,
1358 attributes: value.attributes,
1359 balances: value.balances,
1360 }
1361 }
1362}
1363
1364fn default_include_balances_flag() -> bool {
1365 true
1366}
1367
1368#[derive(Clone, Debug, Serialize, PartialEq, ToSchema, Default, Eq, Hash, DeepSizeOf)]
1370#[serde(deny_unknown_fields)]
1371pub struct ProtocolStateRequestBody {
1372 #[serde(alias = "protocolIds")]
1374 pub protocol_ids: Option<Vec<String>>,
1375 #[serde(alias = "protocolSystem")]
1378 pub protocol_system: String,
1379 #[serde(default)]
1380 pub chain: Chain,
1381 #[serde(default = "default_include_balances_flag")]
1383 pub include_balances: bool,
1384 #[serde(default = "VersionParam::default")]
1385 pub version: VersionParam,
1386 #[serde(default)]
1387 pub pagination: PaginationParams,
1388}
1389
1390impl ProtocolStateRequestBody {
1391 pub fn id_filtered<I, T>(ids: I) -> Self
1392 where
1393 I: IntoIterator<Item = T>,
1394 T: Into<String>,
1395 {
1396 Self {
1397 protocol_ids: Some(
1398 ids.into_iter()
1399 .map(Into::into)
1400 .collect(),
1401 ),
1402 ..Default::default()
1403 }
1404 }
1405}
1406
1407impl<'de> Deserialize<'de> for ProtocolStateRequestBody {
1411 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1412 where
1413 D: Deserializer<'de>,
1414 {
1415 #[derive(Deserialize)]
1416 #[serde(untagged)]
1417 enum ProtocolIdOrString {
1418 Old(Vec<ProtocolId>),
1419 New(Vec<String>),
1420 }
1421
1422 struct ProtocolStateRequestBodyVisitor;
1423
1424 impl<'de> de::Visitor<'de> for ProtocolStateRequestBodyVisitor {
1425 type Value = ProtocolStateRequestBody;
1426
1427 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1428 formatter.write_str("struct ProtocolStateRequestBody")
1429 }
1430
1431 fn visit_map<V>(self, mut map: V) -> Result<ProtocolStateRequestBody, V::Error>
1432 where
1433 V: de::MapAccess<'de>,
1434 {
1435 let mut protocol_ids = None;
1436 let mut protocol_system = None;
1437 let mut version = None;
1438 let mut chain = None;
1439 let mut include_balances = None;
1440 let mut pagination = None;
1441
1442 while let Some(key) = map.next_key::<String>()? {
1443 match key.as_str() {
1444 "protocol_ids" | "protocolIds" => {
1445 let value: ProtocolIdOrString = map.next_value()?;
1446 protocol_ids = match value {
1447 ProtocolIdOrString::Old(ids) => {
1448 Some(ids.into_iter().map(|p| p.id).collect())
1449 }
1450 ProtocolIdOrString::New(ids_str) => Some(ids_str),
1451 };
1452 }
1453 "protocol_system" | "protocolSystem" => {
1454 protocol_system = Some(map.next_value()?);
1455 }
1456 "version" => {
1457 version = Some(map.next_value()?);
1458 }
1459 "chain" => {
1460 chain = Some(map.next_value()?);
1461 }
1462 "include_balances" => {
1463 include_balances = Some(map.next_value()?);
1464 }
1465 "pagination" => {
1466 pagination = Some(map.next_value()?);
1467 }
1468 _ => {
1469 return Err(de::Error::unknown_field(
1470 &key,
1471 &[
1472 "contract_ids",
1473 "protocol_system",
1474 "version",
1475 "chain",
1476 "include_balances",
1477 "pagination",
1478 ],
1479 ))
1480 }
1481 }
1482 }
1483
1484 Ok(ProtocolStateRequestBody {
1485 protocol_ids,
1486 protocol_system: protocol_system.unwrap_or_default(),
1487 version: version.unwrap_or_else(VersionParam::default),
1488 chain: chain.unwrap_or_else(Chain::default),
1489 include_balances: include_balances.unwrap_or(true),
1490 pagination: pagination.unwrap_or_else(PaginationParams::default),
1491 })
1492 }
1493 }
1494
1495 deserializer.deserialize_struct(
1496 "ProtocolStateRequestBody",
1497 &[
1498 "contract_ids",
1499 "protocol_system",
1500 "version",
1501 "chain",
1502 "include_balances",
1503 "pagination",
1504 ],
1505 ProtocolStateRequestBodyVisitor,
1506 )
1507 }
1508}
1509
1510#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, DeepSizeOf)]
1511pub struct ProtocolStateRequestResponse {
1512 pub states: Vec<ResponseProtocolState>,
1513 pub pagination: PaginationResponse,
1514}
1515
1516impl ProtocolStateRequestResponse {
1517 pub fn new(states: Vec<ResponseProtocolState>, pagination: PaginationResponse) -> Self {
1518 Self { states, pagination }
1519 }
1520}
1521
1522#[derive(Serialize, Clone, PartialEq, Hash, Eq)]
1523pub struct ProtocolComponentId {
1524 pub chain: Chain,
1525 pub system: String,
1526 pub id: String,
1527}
1528
1529#[derive(Debug, Serialize, ToSchema)]
1530#[serde(tag = "status", content = "message")]
1531#[schema(example = json!({"status": "NotReady", "message": "No db connection"}))]
1532pub enum Health {
1533 Ready,
1534 Starting(String),
1535 NotReady(String),
1536}
1537
1538#[derive(Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Hash, Clone)]
1539#[serde(deny_unknown_fields)]
1540pub struct ProtocolSystemsRequestBody {
1541 #[serde(default)]
1542 pub chain: Chain,
1543 #[serde(default)]
1544 pub pagination: PaginationParams,
1545}
1546
1547#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, Eq, Hash)]
1548pub struct ProtocolSystemsRequestResponse {
1549 pub protocol_systems: Vec<String>,
1551 pub pagination: PaginationResponse,
1552}
1553
1554impl ProtocolSystemsRequestResponse {
1555 pub fn new(protocol_systems: Vec<String>, pagination: PaginationResponse) -> Self {
1556 Self { protocol_systems, pagination }
1557 }
1558}
1559
1560#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
1561pub struct DCIUpdate {
1562 pub new_entrypoints: HashMap<ComponentId, HashSet<EntryPoint>>,
1564 pub new_entrypoint_params: HashMap<String, HashSet<(TracingParams, Option<String>)>>,
1567 pub trace_results: HashMap<String, TracingResult>,
1569}
1570
1571impl From<models::blockchain::DCIUpdate> for DCIUpdate {
1572 fn from(value: models::blockchain::DCIUpdate) -> Self {
1573 Self {
1574 new_entrypoints: value
1575 .new_entrypoints
1576 .into_iter()
1577 .map(|(k, v)| {
1578 (
1579 k,
1580 v.into_iter()
1581 .map(|v| v.into())
1582 .collect(),
1583 )
1584 })
1585 .collect(),
1586 new_entrypoint_params: value
1587 .new_entrypoint_params
1588 .into_iter()
1589 .map(|(k, v)| {
1590 (
1591 k,
1592 v.into_iter()
1593 .map(|(params, i)| (params.into(), i))
1594 .collect(),
1595 )
1596 })
1597 .collect(),
1598 trace_results: value
1599 .trace_results
1600 .into_iter()
1601 .map(|(k, v)| (k, v.into()))
1602 .collect(),
1603 }
1604 }
1605}
1606
1607#[derive(Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Hash, Clone)]
1608#[serde(deny_unknown_fields)]
1609pub struct ComponentTvlRequestBody {
1610 #[serde(default)]
1611 pub chain: Chain,
1612 #[serde(alias = "protocolSystem")]
1615 pub protocol_system: Option<String>,
1616 #[serde(default)]
1617 pub component_ids: Option<Vec<String>>,
1618 #[serde(default)]
1619 pub pagination: PaginationParams,
1620}
1621
1622impl ComponentTvlRequestBody {
1623 pub fn system_filtered(system: &str, chain: Chain) -> Self {
1624 Self {
1625 chain,
1626 protocol_system: Some(system.to_string()),
1627 component_ids: None,
1628 pagination: Default::default(),
1629 }
1630 }
1631
1632 pub fn id_filtered(ids: Vec<String>, chain: Chain) -> Self {
1633 Self {
1634 chain,
1635 protocol_system: None,
1636 component_ids: Some(ids),
1637 pagination: Default::default(),
1638 }
1639 }
1640}
1641#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema)]
1643pub struct ComponentTvlRequestResponse {
1644 pub tvl: HashMap<String, f64>,
1645 pub pagination: PaginationResponse,
1646}
1647
1648impl ComponentTvlRequestResponse {
1649 pub fn new(tvl: HashMap<String, f64>, pagination: PaginationResponse) -> Self {
1650 Self { tvl, pagination }
1651 }
1652}
1653
1654#[derive(
1655 Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Hash, Clone, DeepSizeOf,
1656)]
1657pub struct TracedEntryPointRequestBody {
1658 #[serde(default)]
1659 pub chain: Chain,
1660 pub protocol_system: String,
1663 #[schema(value_type = Option<Vec<String>>)]
1665 pub component_ids: Option<Vec<ComponentId>>,
1666 #[serde(default)]
1668 pub pagination: PaginationParams,
1669}
1670
1671#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema, Eq, Hash, DeepSizeOf)]
1672pub struct EntryPoint {
1673 #[schema(example = "0xEdf63cce4bA70cbE74064b7687882E71ebB0e988:getRate()")]
1674 pub external_id: String,
1676 #[schema(value_type=String, example="0x8f4E8439b970363648421C692dd897Fb9c0Bd1D9")]
1677 #[serde(with = "hex_bytes")]
1678 pub target: Bytes,
1680 #[schema(example = "getRate()")]
1681 pub signature: String,
1683}
1684
1685#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema, Eq, Hash, DeepSizeOf)]
1686pub enum StorageOverride {
1687 #[schema(value_type=HashMap<String, String>)]
1691 Diff(BTreeMap<StoreKey, StoreVal>),
1692
1693 #[schema(value_type=HashMap<String, String>)]
1697 Replace(BTreeMap<StoreKey, StoreVal>),
1698}
1699
1700impl From<models::blockchain::StorageOverride> for StorageOverride {
1701 fn from(value: models::blockchain::StorageOverride) -> Self {
1702 match value {
1703 models::blockchain::StorageOverride::Diff(diff) => StorageOverride::Diff(diff),
1704 models::blockchain::StorageOverride::Replace(replace) => {
1705 StorageOverride::Replace(replace)
1706 }
1707 }
1708 }
1709}
1710
1711#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema, Eq, Hash, DeepSizeOf)]
1716pub struct AccountOverrides {
1717 pub slots: Option<StorageOverride>,
1719 #[schema(value_type=Option<String>)]
1720 pub native_balance: Option<Balance>,
1722 #[schema(value_type=Option<String>)]
1723 pub code: Option<Code>,
1725}
1726
1727impl From<models::blockchain::AccountOverrides> for AccountOverrides {
1728 fn from(value: models::blockchain::AccountOverrides) -> Self {
1729 AccountOverrides {
1730 slots: value.slots.map(|s| s.into()),
1731 native_balance: value.native_balance,
1732 code: value.code,
1733 }
1734 }
1735}
1736
1737#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema, Eq, Hash, DeepSizeOf)]
1738pub struct RPCTracerParams {
1739 #[schema(value_type=Option<String>)]
1742 #[serde(with = "hex_bytes_option", default)]
1743 pub caller: Option<Bytes>,
1744 #[schema(value_type=String, example="0x679aefce")]
1746 #[serde(with = "hex_bytes")]
1747 pub calldata: Bytes,
1748 pub state_overrides: Option<BTreeMap<Address, AccountOverrides>>,
1750 #[schema(value_type=Option<Vec<String>>)]
1753 #[serde(default)]
1754 pub prune_addresses: Option<Vec<Address>>,
1755}
1756
1757impl From<models::blockchain::RPCTracerParams> for RPCTracerParams {
1758 fn from(value: models::blockchain::RPCTracerParams) -> Self {
1759 RPCTracerParams {
1760 caller: value.caller,
1761 calldata: value.calldata,
1762 state_overrides: value.state_overrides.map(|overrides| {
1763 overrides
1764 .into_iter()
1765 .map(|(address, account_overrides)| (address, account_overrides.into()))
1766 .collect()
1767 }),
1768 prune_addresses: value.prune_addresses,
1769 }
1770 }
1771}
1772
1773#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Hash, DeepSizeOf, ToSchema)]
1774#[serde(tag = "method", rename_all = "lowercase")]
1775pub enum TracingParams {
1776 RPCTracer(RPCTracerParams),
1778}
1779
1780impl From<models::blockchain::TracingParams> for TracingParams {
1781 fn from(value: models::blockchain::TracingParams) -> Self {
1782 match value {
1783 models::blockchain::TracingParams::RPCTracer(params) => {
1784 TracingParams::RPCTracer(params.into())
1785 }
1786 }
1787 }
1788}
1789
1790impl From<models::blockchain::EntryPoint> for EntryPoint {
1791 fn from(value: models::blockchain::EntryPoint) -> Self {
1792 Self { external_id: value.external_id, target: value.target, signature: value.signature }
1793 }
1794}
1795
1796#[derive(Serialize, Deserialize, Debug, PartialEq, ToSchema, Eq, Clone, DeepSizeOf)]
1797pub struct EntryPointWithTracingParams {
1798 pub entry_point: EntryPoint,
1800 pub params: TracingParams,
1802}
1803
1804impl From<models::blockchain::EntryPointWithTracingParams> for EntryPointWithTracingParams {
1805 fn from(value: models::blockchain::EntryPointWithTracingParams) -> Self {
1806 Self { entry_point: value.entry_point.into(), params: value.params.into() }
1807 }
1808}
1809
1810#[derive(
1811 Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize, DeepSizeOf,
1812)]
1813pub struct AddressStorageLocation {
1814 pub key: StoreKey,
1815 pub offset: u8,
1816}
1817
1818impl AddressStorageLocation {
1819 pub fn new(key: StoreKey, offset: u8) -> Self {
1820 Self { key, offset }
1821 }
1822}
1823
1824impl From<models::blockchain::AddressStorageLocation> for AddressStorageLocation {
1825 fn from(value: models::blockchain::AddressStorageLocation) -> Self {
1826 Self { key: value.key, offset: value.offset }
1827 }
1828}
1829
1830fn deserialize_retriggers_from_value(
1831 value: &serde_json::Value,
1832) -> Result<HashSet<(StoreKey, AddressStorageLocation)>, String> {
1833 use serde::Deserialize;
1834 use serde_json::Value;
1835
1836 let mut result = HashSet::new();
1837
1838 if let Value::Array(items) = value {
1839 for item in items {
1840 if let Value::Array(pair) = item {
1841 if pair.len() == 2 {
1842 let key = StoreKey::deserialize(&pair[0])
1843 .map_err(|e| format!("Failed to deserialize key: {}", e))?;
1844
1845 let addr_storage = match &pair[1] {
1847 Value::String(_) => {
1848 let storage_key = StoreKey::deserialize(&pair[1]).map_err(|e| {
1850 format!("Failed to deserialize old format storage key: {}", e)
1851 })?;
1852 AddressStorageLocation::new(storage_key, 12)
1853 }
1854 Value::Object(_) => {
1855 AddressStorageLocation::deserialize(&pair[1]).map_err(|e| {
1857 format!("Failed to deserialize AddressStorageLocation: {}", e)
1858 })?
1859 }
1860 _ => return Err("Invalid retrigger format".to_string()),
1861 };
1862
1863 result.insert((key, addr_storage));
1864 }
1865 }
1866 }
1867 }
1868
1869 Ok(result)
1870}
1871
1872#[derive(Serialize, Debug, Default, PartialEq, ToSchema, Eq, Clone, DeepSizeOf)]
1873pub struct TracingResult {
1874 #[schema(value_type=HashSet<(String, String)>)]
1875 pub retriggers: HashSet<(StoreKey, AddressStorageLocation)>,
1876 #[schema(value_type=HashMap<String,HashSet<String>>)]
1877 pub accessed_slots: HashMap<Address, HashSet<StoreKey>>,
1878}
1879
1880impl<'de> Deserialize<'de> for TracingResult {
1883 fn deserialize<D>(deserializer: D) -> Result<TracingResult, D::Error>
1884 where
1885 D: Deserializer<'de>,
1886 {
1887 use serde::de::Error;
1888 use serde_json::Value;
1889
1890 let value = Value::deserialize(deserializer)?;
1891 let mut result = TracingResult::default();
1892
1893 if let Value::Object(map) = value {
1894 if let Some(retriggers_value) = map.get("retriggers") {
1896 result.retriggers =
1897 deserialize_retriggers_from_value(retriggers_value).map_err(|e| {
1898 D::Error::custom(format!("Failed to deserialize retriggers: {}", e))
1899 })?;
1900 }
1901
1902 if let Some(accessed_slots_value) = map.get("accessed_slots") {
1904 result.accessed_slots = serde_json::from_value(accessed_slots_value.clone())
1905 .map_err(|e| {
1906 D::Error::custom(format!("Failed to deserialize accessed_slots: {}", e))
1907 })?;
1908 }
1909 }
1910
1911 Ok(result)
1912 }
1913}
1914
1915impl From<models::blockchain::TracingResult> for TracingResult {
1916 fn from(value: models::blockchain::TracingResult) -> Self {
1917 TracingResult {
1918 retriggers: value
1919 .retriggers
1920 .into_iter()
1921 .map(|(k, v)| (k, v.into()))
1922 .collect(),
1923 accessed_slots: value.accessed_slots,
1924 }
1925 }
1926}
1927
1928#[derive(Serialize, PartialEq, ToSchema, Eq, Clone, Debug, Deserialize, DeepSizeOf)]
1929pub struct TracedEntryPointRequestResponse {
1930 #[schema(value_type = HashMap<String, Vec<(EntryPointWithTracingParams, TracingResult)>>)]
1933 pub traced_entry_points:
1934 HashMap<ComponentId, Vec<(EntryPointWithTracingParams, TracingResult)>>,
1935 pub pagination: PaginationResponse,
1936}
1937impl From<TracedEntryPointRequestResponse> for DCIUpdate {
1938 fn from(response: TracedEntryPointRequestResponse) -> Self {
1939 let mut new_entrypoints = HashMap::new();
1940 let mut new_entrypoint_params = HashMap::new();
1941 let mut trace_results = HashMap::new();
1942
1943 for (component, traces) in response.traced_entry_points {
1944 let mut entrypoints = HashSet::new();
1945
1946 for (entrypoint, trace) in traces {
1947 let entrypoint_id = entrypoint
1948 .entry_point
1949 .external_id
1950 .clone();
1951
1952 entrypoints.insert(entrypoint.entry_point.clone());
1954
1955 new_entrypoint_params
1957 .entry(entrypoint_id.clone())
1958 .or_insert_with(HashSet::new)
1959 .insert((entrypoint.params, Some(component.clone())));
1960
1961 trace_results
1963 .entry(entrypoint_id)
1964 .and_modify(|existing_trace: &mut TracingResult| {
1965 existing_trace
1967 .retriggers
1968 .extend(trace.retriggers.clone());
1969 for (address, slots) in trace.accessed_slots.clone() {
1970 existing_trace
1971 .accessed_slots
1972 .entry(address)
1973 .or_default()
1974 .extend(slots);
1975 }
1976 })
1977 .or_insert(trace);
1978 }
1979
1980 if !entrypoints.is_empty() {
1981 new_entrypoints.insert(component, entrypoints);
1982 }
1983 }
1984
1985 DCIUpdate { new_entrypoints, new_entrypoint_params, trace_results }
1986 }
1987}
1988
1989#[derive(Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Clone)]
1990pub struct AddEntryPointRequestBody {
1991 #[serde(default)]
1992 pub chain: Chain,
1993 #[schema(value_type=String)]
1994 #[serde(default)]
1995 pub block_hash: Bytes,
1996 #[schema(value_type = Vec<(String, Vec<EntryPointWithTracingParams>)>)]
1998 pub entry_points_with_tracing_data: Vec<(ComponentId, Vec<EntryPointWithTracingParams>)>,
1999}
2000
2001#[derive(Serialize, PartialEq, ToSchema, Eq, Clone, Debug, Deserialize)]
2002pub struct AddEntryPointRequestResponse {
2003 #[schema(value_type = HashMap<String, Vec<(EntryPointWithTracingParams, TracingResult)>>)]
2006 pub traced_entry_points:
2007 HashMap<ComponentId, Vec<(EntryPointWithTracingParams, TracingResult)>>,
2008}
2009
2010#[cfg(test)]
2011mod test {
2012 use std::str::FromStr;
2013
2014 use maplit::hashmap;
2015 use rstest::rstest;
2016
2017 use super::*;
2018
2019 #[test]
2020 fn test_compression_backward_compatibility() {
2021 let json_without_compression = r#"{
2023 "method": "subscribe",
2024 "extractor_id": {
2025 "chain": "ethereum",
2026 "name": "test"
2027 },
2028 "include_state": true
2029 }"#;
2030
2031 let command: Command = serde_json::from_str(json_without_compression)
2032 .expect("Failed to deserialize Subscribe without compression field");
2033
2034 if let Command::Subscribe { compression, .. } = command {
2035 assert_eq!(
2036 compression, false,
2037 "compression should default to false when not specified"
2038 );
2039 } else {
2040 panic!("Expected Subscribe command");
2041 }
2042
2043 let json_with_compression = r#"{
2045 "method": "subscribe",
2046 "extractor_id": {
2047 "chain": "ethereum",
2048 "name": "test"
2049 },
2050 "include_state": true,
2051 "compression": true
2052 }"#;
2053
2054 let command_with_compression: Command = serde_json::from_str(json_with_compression)
2055 .expect("Failed to deserialize Subscribe with compression field");
2056
2057 if let Command::Subscribe { compression, .. } = command_with_compression {
2058 assert_eq!(compression, true, "compression should be true as specified in the JSON");
2059 } else {
2060 panic!("Expected Subscribe command");
2061 }
2062 }
2063
2064 #[test]
2065 fn test_tracing_result_backward_compatibility() {
2066 use serde_json::json;
2067
2068 let old_format_json = json!({
2070 "retriggers": [
2071 ["0x01", "0x02"],
2072 ["0x03", "0x04"]
2073 ],
2074 "accessed_slots": {
2075 "0x05": ["0x06", "0x07"]
2076 }
2077 });
2078
2079 let result: TracingResult = serde_json::from_value(old_format_json).unwrap();
2080
2081 assert_eq!(result.retriggers.len(), 2);
2083 let retriggers_vec: Vec<_> = result.retriggers.iter().collect();
2084 assert!(retriggers_vec.iter().any(|(k, v)| {
2085 k == &Bytes::from("0x01") && v.key == Bytes::from("0x02") && v.offset == 12
2086 }));
2087 assert!(retriggers_vec.iter().any(|(k, v)| {
2088 k == &Bytes::from("0x03") && v.key == Bytes::from("0x04") && v.offset == 12
2089 }));
2090
2091 let new_format_json = json!({
2093 "retriggers": [
2094 ["0x01", {"key": "0x02", "offset": 12}],
2095 ["0x03", {"key": "0x04", "offset": 5}]
2096 ],
2097 "accessed_slots": {
2098 "0x05": ["0x06", "0x07"]
2099 }
2100 });
2101
2102 let result2: TracingResult = serde_json::from_value(new_format_json).unwrap();
2103
2104 assert_eq!(result2.retriggers.len(), 2);
2106 let retriggers_vec2: Vec<_> = result2.retriggers.iter().collect();
2107 assert!(retriggers_vec2.iter().any(|(k, v)| {
2108 k == &Bytes::from("0x01") && v.key == Bytes::from("0x02") && v.offset == 12
2109 }));
2110 assert!(retriggers_vec2.iter().any(|(k, v)| {
2111 k == &Bytes::from("0x03") && v.key == Bytes::from("0x04") && v.offset == 5
2112 }));
2113 }
2114
2115 #[test]
2116 fn test_protocol_components_equality() {
2117 let body1 = ProtocolComponentsRequestBody {
2118 protocol_system: "protocol1".to_string(),
2119 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
2120 tvl_gt: Some(1000.0),
2121 chain: Chain::Ethereum,
2122 pagination: PaginationParams::default(),
2123 };
2124
2125 let body2 = ProtocolComponentsRequestBody {
2126 protocol_system: "protocol1".to_string(),
2127 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
2128 tvl_gt: Some(1000.0 + 1e-7), chain: Chain::Ethereum,
2130 pagination: PaginationParams::default(),
2131 };
2132
2133 assert_eq!(body1, body2);
2135 }
2136
2137 #[test]
2138 fn test_protocol_components_inequality() {
2139 let body1 = ProtocolComponentsRequestBody {
2140 protocol_system: "protocol1".to_string(),
2141 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
2142 tvl_gt: Some(1000.0),
2143 chain: Chain::Ethereum,
2144 pagination: PaginationParams::default(),
2145 };
2146
2147 let body2 = ProtocolComponentsRequestBody {
2148 protocol_system: "protocol1".to_string(),
2149 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
2150 tvl_gt: Some(1000.0 + 1e-5), chain: Chain::Ethereum,
2152 pagination: PaginationParams::default(),
2153 };
2154
2155 assert_ne!(body1, body2);
2157 }
2158
2159 #[test]
2160 fn test_parse_state_request() {
2161 let json_str = r#"
2162 {
2163 "contractIds": [
2164 "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092"
2165 ],
2166 "protocol_system": "uniswap_v2",
2167 "version": {
2168 "timestamp": "2069-01-01T04:20:00",
2169 "block": {
2170 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2171 "number": 213,
2172 "chain": "ethereum"
2173 }
2174 }
2175 }
2176 "#;
2177
2178 let result: StateRequestBody = serde_json::from_str(json_str).unwrap();
2179
2180 let contract0 = "b4eccE46b8D4e4abFd03C9B806276A6735C9c092"
2181 .parse()
2182 .unwrap();
2183 let block_hash = "24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4"
2184 .parse()
2185 .unwrap();
2186 let block_number = 213;
2187
2188 let expected_timestamp =
2189 NaiveDateTime::parse_from_str("2069-01-01T04:20:00", "%Y-%m-%dT%H:%M:%S").unwrap();
2190
2191 let expected = StateRequestBody {
2192 contract_ids: Some(vec![contract0]),
2193 protocol_system: "uniswap_v2".to_string(),
2194 version: VersionParam {
2195 timestamp: Some(expected_timestamp),
2196 block: Some(BlockParam {
2197 hash: Some(block_hash),
2198 chain: Some(Chain::Ethereum),
2199 number: Some(block_number),
2200 }),
2201 },
2202 chain: Chain::Ethereum,
2203 pagination: PaginationParams::default(),
2204 };
2205
2206 assert_eq!(result, expected);
2207 }
2208
2209 #[test]
2210 fn test_parse_state_request_dual_interface() {
2211 let json_common = r#"
2212 {
2213 "__CONTRACT_IDS__": [
2214 "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092"
2215 ],
2216 "version": {
2217 "timestamp": "2069-01-01T04:20:00",
2218 "block": {
2219 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2220 "number": 213,
2221 "chain": "ethereum"
2222 }
2223 }
2224 }
2225 "#;
2226
2227 let json_str_snake = json_common.replace("\"__CONTRACT_IDS__\"", "\"contract_ids\"");
2228 let json_str_camel = json_common.replace("\"__CONTRACT_IDS__\"", "\"contractIds\"");
2229
2230 let snake: StateRequestBody = serde_json::from_str(&json_str_snake).unwrap();
2231 let camel: StateRequestBody = serde_json::from_str(&json_str_camel).unwrap();
2232
2233 assert_eq!(snake, camel);
2234 }
2235
2236 #[test]
2237 fn test_parse_state_request_unknown_field() {
2238 let body = r#"
2239 {
2240 "contract_ids_with_typo_error": [
2241 {
2242 "address": "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092",
2243 "chain": "ethereum"
2244 }
2245 ],
2246 "version": {
2247 "timestamp": "2069-01-01T04:20:00",
2248 "block": {
2249 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2250 "parentHash": "0x8d75152454e60413efe758cc424bfd339897062d7e658f302765eb7b50971815",
2251 "number": 213,
2252 "chain": "ethereum"
2253 }
2254 }
2255 }
2256 "#;
2257
2258 let decoded = serde_json::from_str::<StateRequestBody>(body);
2259
2260 assert!(decoded.is_err(), "Expected an error due to unknown field");
2261
2262 if let Err(e) = decoded {
2263 assert!(
2264 e.to_string()
2265 .contains("unknown field `contract_ids_with_typo_error`"),
2266 "Error message does not contain expected unknown field information"
2267 );
2268 }
2269 }
2270
2271 #[test]
2272 fn test_parse_state_request_no_contract_specified() {
2273 let json_str = r#"
2274 {
2275 "protocol_system": "uniswap_v2",
2276 "version": {
2277 "timestamp": "2069-01-01T04:20:00",
2278 "block": {
2279 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2280 "number": 213,
2281 "chain": "ethereum"
2282 }
2283 }
2284 }
2285 "#;
2286
2287 let result: StateRequestBody = serde_json::from_str(json_str).unwrap();
2288
2289 let block_hash = "24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4".into();
2290 let block_number = 213;
2291 let expected_timestamp =
2292 NaiveDateTime::parse_from_str("2069-01-01T04:20:00", "%Y-%m-%dT%H:%M:%S").unwrap();
2293
2294 let expected = StateRequestBody {
2295 contract_ids: None,
2296 protocol_system: "uniswap_v2".to_string(),
2297 version: VersionParam {
2298 timestamp: Some(expected_timestamp),
2299 block: Some(BlockParam {
2300 hash: Some(block_hash),
2301 chain: Some(Chain::Ethereum),
2302 number: Some(block_number),
2303 }),
2304 },
2305 chain: Chain::Ethereum,
2306 pagination: PaginationParams { page: 0, page_size: 20 },
2307 };
2308
2309 assert_eq!(result, expected);
2310 }
2311
2312 #[rstest]
2313 #[case::deprecated_ids(
2314 r#"
2315 {
2316 "protocol_ids": [
2317 {
2318 "id": "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092",
2319 "chain": "ethereum"
2320 }
2321 ],
2322 "protocol_system": "uniswap_v2",
2323 "include_balances": false,
2324 "version": {
2325 "timestamp": "2069-01-01T04:20:00",
2326 "block": {
2327 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2328 "number": 213,
2329 "chain": "ethereum"
2330 }
2331 }
2332 }
2333 "#
2334 )]
2335 #[case(
2336 r#"
2337 {
2338 "protocolIds": [
2339 "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092"
2340 ],
2341 "protocol_system": "uniswap_v2",
2342 "include_balances": false,
2343 "version": {
2344 "timestamp": "2069-01-01T04:20:00",
2345 "block": {
2346 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2347 "number": 213,
2348 "chain": "ethereum"
2349 }
2350 }
2351 }
2352 "#
2353 )]
2354 fn test_parse_protocol_state_request(#[case] json_str: &str) {
2355 let result: ProtocolStateRequestBody = serde_json::from_str(json_str).unwrap();
2356
2357 let block_hash = "24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4"
2358 .parse()
2359 .unwrap();
2360 let block_number = 213;
2361
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 = ProtocolStateRequestBody {
2366 protocol_ids: Some(vec!["0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092".to_string()]),
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 include_balances: false,
2378 pagination: PaginationParams::default(),
2379 };
2380
2381 assert_eq!(result, expected);
2382 }
2383
2384 #[rstest]
2385 #[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()])]
2386 #[case::with_strings(vec!["id1".to_string(), "id2".to_string()], vec!["id1".to_string(), "id2".to_string()])]
2387 fn test_id_filtered<T>(#[case] input_ids: Vec<T>, #[case] expected_ids: Vec<String>)
2388 where
2389 T: Into<String> + Clone,
2390 {
2391 let request_body = ProtocolStateRequestBody::id_filtered(input_ids);
2392
2393 assert_eq!(request_body.protocol_ids, Some(expected_ids));
2394 }
2395
2396 fn create_models_block_changes() -> crate::models::blockchain::BlockAggregatedChanges {
2397 let base_ts = 1694534400; crate::models::blockchain::BlockAggregatedChanges {
2400 extractor: "native_name".to_string(),
2401 block: models::blockchain::Block::new(
2402 3,
2403 models::Chain::Ethereum,
2404 Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000003").unwrap(),
2405 Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000002").unwrap(),
2406 chrono::DateTime::from_timestamp(base_ts + 3000, 0).unwrap().naive_utc(),
2407 ),
2408 db_committed_block_height: Some(1),
2409 finalized_block_height: 1,
2410 revert: true,
2411 state_deltas: HashMap::from([
2412 ("pc_1".to_string(), models::protocol::ProtocolComponentStateDelta {
2413 component_id: "pc_1".to_string(),
2414 updated_attributes: HashMap::from([
2415 ("attr_2".to_string(), Bytes::from("0x0000000000000002")),
2416 ("attr_1".to_string(), Bytes::from("0x00000000000003e8")),
2417 ]),
2418 deleted_attributes: HashSet::new(),
2419 }),
2420 ]),
2421 new_protocol_components: HashMap::from([
2422 ("pc_2".to_string(), crate::models::protocol::ProtocolComponent {
2423 id: "pc_2".to_string(),
2424 protocol_system: "native_protocol_system".to_string(),
2425 protocol_type_name: "pt_1".to_string(),
2426 chain: models::Chain::Ethereum,
2427 tokens: vec![
2428 Bytes::from_str("0xdac17f958d2ee523a2206206994597c13d831ec7").unwrap(),
2429 Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
2430 ],
2431 contract_addresses: vec![],
2432 static_attributes: HashMap::new(),
2433 change: models::ChangeType::Creation,
2434 creation_tx: Bytes::from_str("0x000000000000000000000000000000000000000000000000000000000000c351").unwrap(),
2435 created_at: chrono::DateTime::from_timestamp(base_ts + 5000, 0).unwrap().naive_utc(),
2436 }),
2437 ]),
2438 deleted_protocol_components: HashMap::from([
2439 ("pc_3".to_string(), crate::models::protocol::ProtocolComponent {
2440 id: "pc_3".to_string(),
2441 protocol_system: "native_protocol_system".to_string(),
2442 protocol_type_name: "pt_2".to_string(),
2443 chain: models::Chain::Ethereum,
2444 tokens: vec![
2445 Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(),
2446 Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(),
2447 ],
2448 contract_addresses: vec![],
2449 static_attributes: HashMap::new(),
2450 change: models::ChangeType::Deletion,
2451 creation_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000009c41").unwrap(),
2452 created_at: chrono::DateTime::from_timestamp(base_ts + 4000, 0).unwrap().naive_utc(),
2453 }),
2454 ]),
2455 component_balances: HashMap::from([
2456 ("pc_1".to_string(), HashMap::from([
2457 (Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(), models::protocol::ComponentBalance {
2458 token: Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
2459 balance: Bytes::from("0x00000001"),
2460 balance_float: 1.0,
2461 modify_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
2462 component_id: "pc_1".to_string(),
2463 }),
2464 (Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(), models::protocol::ComponentBalance {
2465 token: Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(),
2466 balance: Bytes::from("0x000003e8"),
2467 balance_float: 1000.0,
2468 modify_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000007531").unwrap(),
2469 component_id: "pc_1".to_string(),
2470 }),
2471 ])),
2472 ]),
2473 account_balances: HashMap::from([
2474 (Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(), HashMap::from([
2475 (Bytes::from_str("0x7a250d5630b4cf539739df2c5dacb4c659f2488d").unwrap(), models::contract::AccountBalance {
2476 account: Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(),
2477 token: Bytes::from_str("0x7a250d5630b4cf539739df2c5dacb4c659f2488d").unwrap(),
2478 balance: Bytes::from("0x000003e8"),
2479 modify_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000007531").unwrap(),
2480 }),
2481 ])),
2482 ]),
2483 ..Default::default()
2484 }
2485 }
2486
2487 #[test]
2488 fn test_serialize_deserialize_block_changes() {
2489 let block_entity_changes = create_models_block_changes();
2494
2495 let json_data = serde_json::to_string(&block_entity_changes).expect("Failed to serialize");
2497
2498 serde_json::from_str::<BlockChanges>(&json_data).expect("parsing failed");
2500 }
2501
2502 #[test]
2503 fn test_parse_block_changes() {
2504 let json_data = r#"
2505 {
2506 "extractor": "vm:ambient",
2507 "chain": "ethereum",
2508 "block": {
2509 "number": 123,
2510 "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2511 "parent_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2512 "chain": "ethereum",
2513 "ts": "2023-09-14T00:00:00"
2514 },
2515 "finalized_block_height": 0,
2516 "revert": false,
2517 "new_tokens": {},
2518 "account_updates": {
2519 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2520 "address": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2521 "chain": "ethereum",
2522 "slots": {},
2523 "balance": "0x01f4",
2524 "code": "",
2525 "change": "Update"
2526 }
2527 },
2528 "state_updates": {
2529 "component_1": {
2530 "component_id": "component_1",
2531 "updated_attributes": {"attr1": "0x01"},
2532 "deleted_attributes": ["attr2"]
2533 }
2534 },
2535 "new_protocol_components":
2536 { "protocol_1": {
2537 "id": "protocol_1",
2538 "protocol_system": "system_1",
2539 "protocol_type_name": "type_1",
2540 "chain": "ethereum",
2541 "tokens": ["0x01", "0x02"],
2542 "contract_ids": ["0x01", "0x02"],
2543 "static_attributes": {"attr1": "0x01f4"},
2544 "change": "Update",
2545 "creation_tx": "0x01",
2546 "created_at": "2023-09-14T00:00:00"
2547 }
2548 },
2549 "deleted_protocol_components": {},
2550 "component_balances": {
2551 "protocol_1":
2552 {
2553 "0x01": {
2554 "token": "0x01",
2555 "balance": "0xb77831d23691653a01",
2556 "balance_float": 3.3844151001790677e21,
2557 "modify_tx": "0x01",
2558 "component_id": "protocol_1"
2559 }
2560 }
2561 },
2562 "account_balances": {
2563 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2564 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2565 "account": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2566 "token": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2567 "balance": "0x01f4",
2568 "modify_tx": "0x01"
2569 }
2570 }
2571 },
2572 "component_tvl": {
2573 "protocol_1": 1000.0
2574 },
2575 "dci_update": {
2576 "new_entrypoints": {
2577 "component_1": [
2578 {
2579 "external_id": "0x01:sig()",
2580 "target": "0x01",
2581 "signature": "sig()"
2582 }
2583 ]
2584 },
2585 "new_entrypoint_params": {
2586 "0x01:sig()": [
2587 [
2588 {
2589 "method": "rpctracer",
2590 "caller": "0x01",
2591 "calldata": "0x02"
2592 },
2593 "component_1"
2594 ]
2595 ]
2596 },
2597 "trace_results": {
2598 "0x01:sig()": {
2599 "retriggers": [
2600 ["0x01", {"key": "0x02", "offset": 12}]
2601 ],
2602 "accessed_slots": {
2603 "0x03": ["0x03", "0x04"]
2604 }
2605 }
2606 }
2607 }
2608 }
2609 "#;
2610
2611 serde_json::from_str::<BlockChanges>(json_data).expect("parsing failed");
2612 }
2613
2614 #[test]
2615 fn test_parse_websocket_message() {
2616 let json_data = r#"
2617 {
2618 "subscription_id": "5d23bfbe-89ad-4ea3-8672-dc9e973ac9dc",
2619 "deltas": {
2620 "type": "BlockChanges",
2621 "extractor": "uniswap_v2",
2622 "chain": "ethereum",
2623 "block": {
2624 "number": 19291517,
2625 "hash": "0xbc3ea4896c0be8da6229387a8571b72818aa258daf4fab46471003ad74c4ee83",
2626 "parent_hash": "0x89ca5b8d593574cf6c886f41ef8208bf6bdc1a90ef36046cb8c84bc880b9af8f",
2627 "chain": "ethereum",
2628 "ts": "2024-02-23T16:35:35"
2629 },
2630 "finalized_block_height": 0,
2631 "revert": false,
2632 "new_tokens": {},
2633 "account_updates": {
2634 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2635 "address": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2636 "chain": "ethereum",
2637 "slots": {},
2638 "balance": "0x01f4",
2639 "code": "",
2640 "change": "Update"
2641 }
2642 },
2643 "state_updates": {
2644 "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28": {
2645 "component_id": "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28",
2646 "updated_attributes": {
2647 "reserve0": "0x87f7b5973a7f28a8b32404",
2648 "reserve1": "0x09e9564b11"
2649 },
2650 "deleted_attributes": []
2651 },
2652 "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d": {
2653 "component_id": "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d",
2654 "updated_attributes": {
2655 "reserve1": "0x44d9a8fd662c2f4d03",
2656 "reserve0": "0x500b1261f811d5bf423e"
2657 },
2658 "deleted_attributes": []
2659 }
2660 },
2661 "new_protocol_components": {},
2662 "deleted_protocol_components": {},
2663 "component_balances": {
2664 "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d": {
2665 "0x9012744b7a564623b6c3e40b144fc196bdedf1a9": {
2666 "token": "0x9012744b7a564623b6c3e40b144fc196bdedf1a9",
2667 "balance": "0x500b1261f811d5bf423e",
2668 "balance_float": 3.779935574269033E23,
2669 "modify_tx": "0xe46c4db085fb6c6f3408a65524555797adb264e1d5cf3b66ad154598f85ac4bf",
2670 "component_id": "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d"
2671 },
2672 "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": {
2673 "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
2674 "balance": "0x44d9a8fd662c2f4d03",
2675 "balance_float": 1.270062661329837E21,
2676 "modify_tx": "0xe46c4db085fb6c6f3408a65524555797adb264e1d5cf3b66ad154598f85ac4bf",
2677 "component_id": "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d"
2678 }
2679 }
2680 },
2681 "account_balances": {
2682 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2683 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2684 "account": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2685 "token": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2686 "balance": "0x01f4",
2687 "modify_tx": "0x01"
2688 }
2689 }
2690 },
2691 "component_tvl": {},
2692 "dci_update": {
2693 "new_entrypoints": {
2694 "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28": [
2695 {
2696 "external_id": "0x01:sig()",
2697 "target": "0x01",
2698 "signature": "sig()"
2699 }
2700 ]
2701 },
2702 "new_entrypoint_params": {
2703 "0x01:sig()": [
2704 [
2705 {
2706 "method": "rpctracer",
2707 "caller": "0x01",
2708 "calldata": "0x02"
2709 },
2710 "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28"
2711 ]
2712 ]
2713 },
2714 "trace_results": {
2715 "0x01:sig()": {
2716 "retriggers": [
2717 ["0x01", {"key": "0x02", "offset": 12}]
2718 ],
2719 "accessed_slots": {
2720 "0x03": ["0x03", "0x04"]
2721 }
2722 }
2723 }
2724 }
2725 }
2726 }
2727 "#;
2728 serde_json::from_str::<WebSocketMessage>(json_data).expect("parsing failed");
2729 }
2730
2731 #[test]
2732 fn test_protocol_state_delta_merge_update_delete() {
2733 let mut delta1 = ProtocolStateDelta {
2735 component_id: "Component1".to_string(),
2736 updated_attributes: HashMap::from([(
2737 "Attribute1".to_string(),
2738 Bytes::from("0xbadbabe420"),
2739 )]),
2740 deleted_attributes: HashSet::new(),
2741 };
2742 let delta2 = ProtocolStateDelta {
2743 component_id: "Component1".to_string(),
2744 updated_attributes: HashMap::from([(
2745 "Attribute2".to_string(),
2746 Bytes::from("0x0badbabe"),
2747 )]),
2748 deleted_attributes: HashSet::from(["Attribute1".to_string()]),
2749 };
2750 let exp = ProtocolStateDelta {
2751 component_id: "Component1".to_string(),
2752 updated_attributes: HashMap::from([(
2753 "Attribute2".to_string(),
2754 Bytes::from("0x0badbabe"),
2755 )]),
2756 deleted_attributes: HashSet::from(["Attribute1".to_string()]),
2757 };
2758
2759 delta1.merge(&delta2);
2760
2761 assert_eq!(delta1, exp);
2762 }
2763
2764 #[test]
2765 fn test_protocol_state_delta_merge_delete_update() {
2766 let mut delta1 = ProtocolStateDelta {
2768 component_id: "Component1".to_string(),
2769 updated_attributes: HashMap::new(),
2770 deleted_attributes: HashSet::from(["Attribute1".to_string()]),
2771 };
2772 let delta2 = ProtocolStateDelta {
2773 component_id: "Component1".to_string(),
2774 updated_attributes: HashMap::from([(
2775 "Attribute1".to_string(),
2776 Bytes::from("0x0badbabe"),
2777 )]),
2778 deleted_attributes: HashSet::new(),
2779 };
2780 let exp = ProtocolStateDelta {
2781 component_id: "Component1".to_string(),
2782 updated_attributes: HashMap::from([(
2783 "Attribute1".to_string(),
2784 Bytes::from("0x0badbabe"),
2785 )]),
2786 deleted_attributes: HashSet::new(),
2787 };
2788
2789 delta1.merge(&delta2);
2790
2791 assert_eq!(delta1, exp);
2792 }
2793
2794 #[test]
2795 fn test_account_update_merge() {
2796 let mut account1 = AccountUpdate::new(
2798 Bytes::from(b"0x1234"),
2799 Chain::Ethereum,
2800 HashMap::from([(Bytes::from("0xaabb"), Bytes::from("0xccdd"))]),
2801 Some(Bytes::from("0x1000")),
2802 Some(Bytes::from("0xdeadbeaf")),
2803 ChangeType::Creation,
2804 );
2805
2806 let account2 = AccountUpdate::new(
2807 Bytes::from(b"0x1234"), Chain::Ethereum,
2809 HashMap::from([(Bytes::from("0xeeff"), Bytes::from("0x11223344"))]),
2810 Some(Bytes::from("0x2000")),
2811 Some(Bytes::from("0xcafebabe")),
2812 ChangeType::Update,
2813 );
2814
2815 account1.merge(&account2);
2817
2818 let expected = AccountUpdate::new(
2820 Bytes::from(b"0x1234"), Chain::Ethereum,
2822 HashMap::from([
2823 (Bytes::from("0xaabb"), Bytes::from("0xccdd")), (Bytes::from("0xeeff"), Bytes::from("0x11223344")), ]),
2826 Some(Bytes::from("0x2000")), Some(Bytes::from("0xcafebabe")), ChangeType::Creation, );
2830
2831 assert_eq!(account1, expected);
2833 }
2834
2835 #[test]
2836 fn test_block_account_changes_merge() {
2837 let old_account_updates: HashMap<Bytes, AccountUpdate> = [(
2839 Bytes::from("0x0011"),
2840 AccountUpdate {
2841 address: Bytes::from("0x00"),
2842 chain: Chain::Ethereum,
2843 slots: HashMap::from([(Bytes::from("0x0022"), Bytes::from("0x0033"))]),
2844 balance: Some(Bytes::from("0x01")),
2845 code: Some(Bytes::from("0x02")),
2846 change: ChangeType::Creation,
2847 },
2848 )]
2849 .into_iter()
2850 .collect();
2851 let new_account_updates: HashMap<Bytes, AccountUpdate> = [(
2852 Bytes::from("0x0011"),
2853 AccountUpdate {
2854 address: Bytes::from("0x00"),
2855 chain: Chain::Ethereum,
2856 slots: HashMap::from([(Bytes::from("0x0044"), Bytes::from("0x0055"))]),
2857 balance: Some(Bytes::from("0x03")),
2858 code: Some(Bytes::from("0x04")),
2859 change: ChangeType::Update,
2860 },
2861 )]
2862 .into_iter()
2863 .collect();
2864 let block_account_changes_initial = BlockChanges {
2866 extractor: "extractor1".to_string(),
2867 revert: false,
2868 account_updates: old_account_updates,
2869 ..Default::default()
2870 };
2871
2872 let block_account_changes_new = BlockChanges {
2873 extractor: "extractor2".to_string(),
2874 revert: true,
2875 account_updates: new_account_updates,
2876 ..Default::default()
2877 };
2878
2879 let res = block_account_changes_initial.merge(block_account_changes_new);
2881
2882 let expected_account_updates: HashMap<Bytes, AccountUpdate> = [(
2884 Bytes::from("0x0011"),
2885 AccountUpdate {
2886 address: Bytes::from("0x00"),
2887 chain: Chain::Ethereum,
2888 slots: HashMap::from([
2889 (Bytes::from("0x0044"), Bytes::from("0x0055")),
2890 (Bytes::from("0x0022"), Bytes::from("0x0033")),
2891 ]),
2892 balance: Some(Bytes::from("0x03")),
2893 code: Some(Bytes::from("0x04")),
2894 change: ChangeType::Creation,
2895 },
2896 )]
2897 .into_iter()
2898 .collect();
2899 let block_account_changes_expected = BlockChanges {
2900 extractor: "extractor1".to_string(),
2901 revert: true,
2902 account_updates: expected_account_updates,
2903 ..Default::default()
2904 };
2905 assert_eq!(res, block_account_changes_expected);
2906 }
2907
2908 #[test]
2909 fn test_block_entity_changes_merge() {
2910 let block_entity_changes_result1 = BlockChanges {
2912 extractor: String::from("extractor1"),
2913 revert: false,
2914 state_updates: hashmap! { "state1".to_string() => ProtocolStateDelta::default() },
2915 new_protocol_components: hashmap! { "component1".to_string() => ProtocolComponent::default() },
2916 deleted_protocol_components: HashMap::new(),
2917 component_balances: hashmap! {
2918 "component1".to_string() => TokenBalances(hashmap! {
2919 Bytes::from("0x01") => ComponentBalance {
2920 token: Bytes::from("0x01"),
2921 balance: Bytes::from("0x01"),
2922 balance_float: 1.0,
2923 modify_tx: Bytes::from("0x00"),
2924 component_id: "component1".to_string()
2925 },
2926 Bytes::from("0x02") => ComponentBalance {
2927 token: Bytes::from("0x02"),
2928 balance: Bytes::from("0x02"),
2929 balance_float: 2.0,
2930 modify_tx: Bytes::from("0x00"),
2931 component_id: "component1".to_string()
2932 },
2933 })
2934
2935 },
2936 component_tvl: hashmap! { "tvl1".to_string() => 1000.0 },
2937 ..Default::default()
2938 };
2939 let block_entity_changes_result2 = BlockChanges {
2940 extractor: String::from("extractor2"),
2941 revert: true,
2942 state_updates: hashmap! { "state2".to_string() => ProtocolStateDelta::default() },
2943 new_protocol_components: hashmap! { "component2".to_string() => ProtocolComponent::default() },
2944 deleted_protocol_components: hashmap! { "component3".to_string() => ProtocolComponent::default() },
2945 component_balances: hashmap! {
2946 "component1".to_string() => TokenBalances::default(),
2947 "component2".to_string() => TokenBalances::default()
2948 },
2949 component_tvl: hashmap! { "tvl2".to_string() => 2000.0 },
2950 ..Default::default()
2951 };
2952
2953 let res = block_entity_changes_result1.merge(block_entity_changes_result2);
2954
2955 let expected_block_entity_changes_result = BlockChanges {
2956 extractor: String::from("extractor1"),
2957 revert: true,
2958 state_updates: hashmap! {
2959 "state1".to_string() => ProtocolStateDelta::default(),
2960 "state2".to_string() => ProtocolStateDelta::default(),
2961 },
2962 new_protocol_components: hashmap! {
2963 "component1".to_string() => ProtocolComponent::default(),
2964 "component2".to_string() => ProtocolComponent::default(),
2965 },
2966 deleted_protocol_components: hashmap! {
2967 "component3".to_string() => ProtocolComponent::default(),
2968 },
2969 component_balances: hashmap! {
2970 "component1".to_string() => TokenBalances(hashmap! {
2971 Bytes::from("0x01") => ComponentBalance {
2972 token: Bytes::from("0x01"),
2973 balance: Bytes::from("0x01"),
2974 balance_float: 1.0,
2975 modify_tx: Bytes::from("0x00"),
2976 component_id: "component1".to_string()
2977 },
2978 Bytes::from("0x02") => ComponentBalance {
2979 token: Bytes::from("0x02"),
2980 balance: Bytes::from("0x02"),
2981 balance_float: 2.0,
2982 modify_tx: Bytes::from("0x00"),
2983 component_id: "component1".to_string()
2984 },
2985 }),
2986 "component2".to_string() => TokenBalances::default(),
2987 },
2988 component_tvl: hashmap! {
2989 "tvl1".to_string() => 1000.0,
2990 "tvl2".to_string() => 2000.0
2991 },
2992 ..Default::default()
2993 };
2994
2995 assert_eq!(res, expected_block_entity_changes_result);
2996 }
2997
2998 #[test]
2999 fn test_websocket_error_serialization() {
3000 let extractor_id = ExtractorIdentity::new(Chain::Ethereum, "test_extractor");
3001 let subscription_id = Uuid::new_v4();
3002
3003 let error = WebsocketError::ExtractorNotFound(extractor_id.clone());
3005 let json = serde_json::to_string(&error).unwrap();
3006 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
3007 assert_eq!(error, deserialized);
3008
3009 let error = WebsocketError::SubscriptionNotFound(subscription_id);
3011 let json = serde_json::to_string(&error).unwrap();
3012 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
3013 assert_eq!(error, deserialized);
3014
3015 let error = WebsocketError::ParseError("{asd".to_string(), "invalid json".to_string());
3017 let json = serde_json::to_string(&error).unwrap();
3018 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
3019 assert_eq!(error, deserialized);
3020
3021 let error = WebsocketError::SubscribeError(extractor_id.clone());
3023 let json = serde_json::to_string(&error).unwrap();
3024 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
3025 assert_eq!(error, deserialized);
3026
3027 let error =
3029 WebsocketError::CompressionError(subscription_id, "Compression failed".to_string());
3030 let json = serde_json::to_string(&error).unwrap();
3031 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
3032 assert_eq!(error, deserialized);
3033 }
3034
3035 #[test]
3036 fn test_websocket_message_with_error_response() {
3037 let error =
3038 WebsocketError::ParseError("}asdfas".to_string(), "malformed request".to_string());
3039 let response = Response::Error(error.clone());
3040 let message = WebSocketMessage::Response(response);
3041
3042 let json = serde_json::to_string(&message).unwrap();
3043 let deserialized: WebSocketMessage = serde_json::from_str(&json).unwrap();
3044
3045 if let WebSocketMessage::Response(Response::Error(deserialized_error)) = deserialized {
3046 assert_eq!(error, deserialized_error);
3047 } else {
3048 panic!("Expected WebSocketMessage::Response(Response::Error)");
3049 }
3050 }
3051
3052 #[test]
3053 fn test_websocket_error_conversion_from_models() {
3054 use crate::models::error::WebsocketError as ModelsError;
3055
3056 let extractor_id =
3057 crate::models::ExtractorIdentity::new(crate::models::Chain::Ethereum, "test");
3058 let subscription_id = Uuid::new_v4();
3059
3060 let models_error = ModelsError::ExtractorNotFound(extractor_id.clone());
3062 let dto_error: WebsocketError = models_error.into();
3063 assert_eq!(dto_error, WebsocketError::ExtractorNotFound(extractor_id.clone().into()));
3064
3065 let models_error = ModelsError::SubscriptionNotFound(subscription_id);
3067 let dto_error: WebsocketError = models_error.into();
3068 assert_eq!(dto_error, WebsocketError::SubscriptionNotFound(subscription_id));
3069
3070 let json_result: Result<serde_json::Value, _> = serde_json::from_str("{invalid json");
3072 let json_error = json_result.unwrap_err();
3073 let models_error = ModelsError::ParseError("{invalid json".to_string(), json_error);
3074 let dto_error: WebsocketError = models_error.into();
3075 if let WebsocketError::ParseError(msg, error) = dto_error {
3076 assert!(!error.is_empty(), "Error message should not be empty, got: '{}'", msg);
3078 } else {
3079 panic!("Expected ParseError variant");
3080 }
3081
3082 let models_error = ModelsError::SubscribeError(extractor_id.clone());
3084 let dto_error: WebsocketError = models_error.into();
3085 assert_eq!(dto_error, WebsocketError::SubscribeError(extractor_id.into()));
3086
3087 let io_error = std::io::Error::other("Compression failed");
3089 let models_error = ModelsError::CompressionError(subscription_id, io_error);
3090 let dto_error: WebsocketError = models_error.into();
3091 if let WebsocketError::CompressionError(sub_id, msg) = &dto_error {
3092 assert_eq!(*sub_id, subscription_id);
3093 assert!(msg.contains("Compression failed"));
3094 } else {
3095 panic!("Expected CompressionError variant");
3096 }
3097 }
3098}
3099
3100#[cfg(test)]
3101mod memory_size_tests {
3102 use std::collections::HashMap;
3103
3104 use super::*;
3105
3106 #[test]
3107 fn test_state_request_response_memory_size_empty() {
3108 let response = StateRequestResponse {
3109 accounts: vec![],
3110 pagination: PaginationResponse::new(1, 10, 0),
3111 };
3112
3113 let size = response.deep_size_of();
3114
3115 assert!(size >= 48, "Empty response should have minimum size of 48 bytes, got {}", size);
3117 assert!(size < 200, "Empty response should not be too large, got {}", size);
3118 }
3119
3120 #[test]
3121 fn test_state_request_response_memory_size_scales_with_slots() {
3122 let create_response_with_slots = |slot_count: usize| {
3123 let mut slots = HashMap::new();
3124 for i in 0..slot_count {
3125 let key = vec![i as u8; 32]; let value = vec![(i + 100) as u8; 32]; slots.insert(key.into(), value.into());
3128 }
3129
3130 let account = ResponseAccount::new(
3131 Chain::Ethereum,
3132 vec![1; 20].into(),
3133 "Pool".to_string(),
3134 slots,
3135 vec![1; 32].into(),
3136 HashMap::new(),
3137 vec![].into(), vec![1; 32].into(),
3139 vec![1; 32].into(),
3140 vec![1; 32].into(),
3141 None,
3142 );
3143
3144 StateRequestResponse {
3145 accounts: vec![account],
3146 pagination: PaginationResponse::new(1, 10, 1),
3147 }
3148 };
3149
3150 let small_response = create_response_with_slots(10);
3151 let large_response = create_response_with_slots(100);
3152
3153 let small_size = small_response.deep_size_of();
3154 let large_size = large_response.deep_size_of();
3155
3156 assert!(
3158 large_size > small_size * 5,
3159 "Large response ({} bytes) should be much larger than small response ({} bytes)",
3160 large_size,
3161 small_size
3162 );
3163
3164 let size_diff = large_size - small_size;
3166 let expected_min_diff = 90 * 64; assert!(
3168 size_diff > expected_min_diff,
3169 "Size difference ({} bytes) should reflect the additional slot data",
3170 size_diff
3171 );
3172 }
3173}