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