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}
1915impl From<TracedEntryPointRequestResponse> for DCIUpdate {
1916 fn from(response: TracedEntryPointRequestResponse) -> Self {
1917 let mut new_entrypoints = HashMap::new();
1918 let mut new_entrypoint_params = HashMap::new();
1919 let mut trace_results = HashMap::new();
1920
1921 for (component, traces) in response.traced_entry_points {
1922 let mut entrypoints = HashSet::new();
1923
1924 for (entrypoint, trace) in traces {
1925 let entrypoint_id = entrypoint
1926 .entry_point
1927 .external_id
1928 .clone();
1929
1930 entrypoints.insert(entrypoint.entry_point.clone());
1932
1933 new_entrypoint_params
1935 .entry(entrypoint_id.clone())
1936 .or_insert_with(HashSet::new)
1937 .insert((entrypoint.params, Some(component.clone())));
1938
1939 trace_results
1941 .entry(entrypoint_id)
1942 .and_modify(|existing_trace: &mut TracingResult| {
1943 existing_trace
1945 .retriggers
1946 .extend(trace.retriggers.clone());
1947 for (address, slots) in trace.accessed_slots.clone() {
1948 existing_trace
1949 .accessed_slots
1950 .entry(address)
1951 .or_default()
1952 .extend(slots);
1953 }
1954 })
1955 .or_insert(trace);
1956 }
1957
1958 if !entrypoints.is_empty() {
1959 new_entrypoints.insert(component, entrypoints);
1960 }
1961 }
1962
1963 DCIUpdate { new_entrypoints, new_entrypoint_params, trace_results }
1964 }
1965}
1966
1967#[derive(Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Clone)]
1968pub struct AddEntryPointRequestBody {
1969 #[serde(default)]
1970 pub chain: Chain,
1971 #[schema(value_type=String)]
1972 #[serde(default)]
1973 pub block_hash: Bytes,
1974 pub entry_points_with_tracing_data: Vec<(ComponentId, Vec<EntryPointWithTracingParams>)>,
1976}
1977
1978#[derive(Serialize, PartialEq, ToSchema, Eq, Clone, Debug, Deserialize)]
1979pub struct AddEntryPointRequestResponse {
1980 pub traced_entry_points:
1983 HashMap<ComponentId, Vec<(EntryPointWithTracingParams, TracingResult)>>,
1984}
1985
1986#[cfg(test)]
1987mod test {
1988 use std::str::FromStr;
1989
1990 use maplit::hashmap;
1991 use rstest::rstest;
1992
1993 use super::*;
1994
1995 #[test]
1996 fn test_tracing_result_backward_compatibility() {
1997 use serde_json::json;
1998
1999 let old_format_json = json!({
2001 "retriggers": [
2002 ["0x01", "0x02"],
2003 ["0x03", "0x04"]
2004 ],
2005 "accessed_slots": {
2006 "0x05": ["0x06", "0x07"]
2007 }
2008 });
2009
2010 let result: TracingResult = serde_json::from_value(old_format_json).unwrap();
2011
2012 assert_eq!(result.retriggers.len(), 2);
2014 let retriggers_vec: Vec<_> = result.retriggers.iter().collect();
2015 assert!(retriggers_vec.iter().any(|(k, v)| {
2016 k == &Bytes::from("0x01") && v.key == Bytes::from("0x02") && v.offset == 12
2017 }));
2018 assert!(retriggers_vec.iter().any(|(k, v)| {
2019 k == &Bytes::from("0x03") && v.key == Bytes::from("0x04") && v.offset == 12
2020 }));
2021
2022 let new_format_json = json!({
2024 "retriggers": [
2025 ["0x01", {"key": "0x02", "offset": 12}],
2026 ["0x03", {"key": "0x04", "offset": 5}]
2027 ],
2028 "accessed_slots": {
2029 "0x05": ["0x06", "0x07"]
2030 }
2031 });
2032
2033 let result2: TracingResult = serde_json::from_value(new_format_json).unwrap();
2034
2035 assert_eq!(result2.retriggers.len(), 2);
2037 let retriggers_vec2: Vec<_> = result2.retriggers.iter().collect();
2038 assert!(retriggers_vec2.iter().any(|(k, v)| {
2039 k == &Bytes::from("0x01") && v.key == Bytes::from("0x02") && v.offset == 12
2040 }));
2041 assert!(retriggers_vec2.iter().any(|(k, v)| {
2042 k == &Bytes::from("0x03") && v.key == Bytes::from("0x04") && v.offset == 5
2043 }));
2044 }
2045
2046 #[test]
2047 fn test_protocol_components_equality() {
2048 let body1 = ProtocolComponentsRequestBody {
2049 protocol_system: "protocol1".to_string(),
2050 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
2051 tvl_gt: Some(1000.0),
2052 chain: Chain::Ethereum,
2053 pagination: PaginationParams::default(),
2054 };
2055
2056 let body2 = ProtocolComponentsRequestBody {
2057 protocol_system: "protocol1".to_string(),
2058 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
2059 tvl_gt: Some(1000.0 + 1e-7), chain: Chain::Ethereum,
2061 pagination: PaginationParams::default(),
2062 };
2063
2064 assert_eq!(body1, body2);
2066 }
2067
2068 #[test]
2069 fn test_protocol_components_inequality() {
2070 let body1 = ProtocolComponentsRequestBody {
2071 protocol_system: "protocol1".to_string(),
2072 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
2073 tvl_gt: Some(1000.0),
2074 chain: Chain::Ethereum,
2075 pagination: PaginationParams::default(),
2076 };
2077
2078 let body2 = ProtocolComponentsRequestBody {
2079 protocol_system: "protocol1".to_string(),
2080 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
2081 tvl_gt: Some(1000.0 + 1e-5), chain: Chain::Ethereum,
2083 pagination: PaginationParams::default(),
2084 };
2085
2086 assert_ne!(body1, body2);
2088 }
2089
2090 #[test]
2091 fn test_parse_state_request() {
2092 let json_str = r#"
2093 {
2094 "contractIds": [
2095 "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092"
2096 ],
2097 "protocol_system": "uniswap_v2",
2098 "version": {
2099 "timestamp": "2069-01-01T04:20:00",
2100 "block": {
2101 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2102 "number": 213,
2103 "chain": "ethereum"
2104 }
2105 }
2106 }
2107 "#;
2108
2109 let result: StateRequestBody = serde_json::from_str(json_str).unwrap();
2110
2111 let contract0 = "b4eccE46b8D4e4abFd03C9B806276A6735C9c092"
2112 .parse()
2113 .unwrap();
2114 let block_hash = "24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4"
2115 .parse()
2116 .unwrap();
2117 let block_number = 213;
2118
2119 let expected_timestamp =
2120 NaiveDateTime::parse_from_str("2069-01-01T04:20:00", "%Y-%m-%dT%H:%M:%S").unwrap();
2121
2122 let expected = StateRequestBody {
2123 contract_ids: Some(vec![contract0]),
2124 protocol_system: "uniswap_v2".to_string(),
2125 version: VersionParam {
2126 timestamp: Some(expected_timestamp),
2127 block: Some(BlockParam {
2128 hash: Some(block_hash),
2129 chain: Some(Chain::Ethereum),
2130 number: Some(block_number),
2131 }),
2132 },
2133 chain: Chain::Ethereum,
2134 pagination: PaginationParams::default(),
2135 };
2136
2137 assert_eq!(result, expected);
2138 }
2139
2140 #[test]
2141 fn test_parse_state_request_dual_interface() {
2142 let json_common = r#"
2143 {
2144 "__CONTRACT_IDS__": [
2145 "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092"
2146 ],
2147 "version": {
2148 "timestamp": "2069-01-01T04:20:00",
2149 "block": {
2150 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2151 "number": 213,
2152 "chain": "ethereum"
2153 }
2154 }
2155 }
2156 "#;
2157
2158 let json_str_snake = json_common.replace("\"__CONTRACT_IDS__\"", "\"contract_ids\"");
2159 let json_str_camel = json_common.replace("\"__CONTRACT_IDS__\"", "\"contractIds\"");
2160
2161 let snake: StateRequestBody = serde_json::from_str(&json_str_snake).unwrap();
2162 let camel: StateRequestBody = serde_json::from_str(&json_str_camel).unwrap();
2163
2164 assert_eq!(snake, camel);
2165 }
2166
2167 #[test]
2168 fn test_parse_state_request_unknown_field() {
2169 let body = r#"
2170 {
2171 "contract_ids_with_typo_error": [
2172 {
2173 "address": "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092",
2174 "chain": "ethereum"
2175 }
2176 ],
2177 "version": {
2178 "timestamp": "2069-01-01T04:20:00",
2179 "block": {
2180 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2181 "parentHash": "0x8d75152454e60413efe758cc424bfd339897062d7e658f302765eb7b50971815",
2182 "number": 213,
2183 "chain": "ethereum"
2184 }
2185 }
2186 }
2187 "#;
2188
2189 let decoded = serde_json::from_str::<StateRequestBody>(body);
2190
2191 assert!(decoded.is_err(), "Expected an error due to unknown field");
2192
2193 if let Err(e) = decoded {
2194 assert!(
2195 e.to_string()
2196 .contains("unknown field `contract_ids_with_typo_error`"),
2197 "Error message does not contain expected unknown field information"
2198 );
2199 }
2200 }
2201
2202 #[test]
2203 fn test_parse_state_request_no_contract_specified() {
2204 let json_str = r#"
2205 {
2206 "protocol_system": "uniswap_v2",
2207 "version": {
2208 "timestamp": "2069-01-01T04:20:00",
2209 "block": {
2210 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2211 "number": 213,
2212 "chain": "ethereum"
2213 }
2214 }
2215 }
2216 "#;
2217
2218 let result: StateRequestBody = serde_json::from_str(json_str).unwrap();
2219
2220 let block_hash = "24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4".into();
2221 let block_number = 213;
2222 let expected_timestamp =
2223 NaiveDateTime::parse_from_str("2069-01-01T04:20:00", "%Y-%m-%dT%H:%M:%S").unwrap();
2224
2225 let expected = StateRequestBody {
2226 contract_ids: None,
2227 protocol_system: "uniswap_v2".to_string(),
2228 version: VersionParam {
2229 timestamp: Some(expected_timestamp),
2230 block: Some(BlockParam {
2231 hash: Some(block_hash),
2232 chain: Some(Chain::Ethereum),
2233 number: Some(block_number),
2234 }),
2235 },
2236 chain: Chain::Ethereum,
2237 pagination: PaginationParams { page: 0, page_size: 20 },
2238 };
2239
2240 assert_eq!(result, expected);
2241 }
2242
2243 #[rstest]
2244 #[case::deprecated_ids(
2245 r#"
2246 {
2247 "protocol_ids": [
2248 {
2249 "id": "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092",
2250 "chain": "ethereum"
2251 }
2252 ],
2253 "protocol_system": "uniswap_v2",
2254 "include_balances": false,
2255 "version": {
2256 "timestamp": "2069-01-01T04:20:00",
2257 "block": {
2258 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2259 "number": 213,
2260 "chain": "ethereum"
2261 }
2262 }
2263 }
2264 "#
2265 )]
2266 #[case(
2267 r#"
2268 {
2269 "protocolIds": [
2270 "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092"
2271 ],
2272 "protocol_system": "uniswap_v2",
2273 "include_balances": false,
2274 "version": {
2275 "timestamp": "2069-01-01T04:20:00",
2276 "block": {
2277 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2278 "number": 213,
2279 "chain": "ethereum"
2280 }
2281 }
2282 }
2283 "#
2284 )]
2285 fn test_parse_protocol_state_request(#[case] json_str: &str) {
2286 let result: ProtocolStateRequestBody = serde_json::from_str(json_str).unwrap();
2287
2288 let block_hash = "24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4"
2289 .parse()
2290 .unwrap();
2291 let block_number = 213;
2292
2293 let expected_timestamp =
2294 NaiveDateTime::parse_from_str("2069-01-01T04:20:00", "%Y-%m-%dT%H:%M:%S").unwrap();
2295
2296 let expected = ProtocolStateRequestBody {
2297 protocol_ids: Some(vec!["0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092".to_string()]),
2298 protocol_system: "uniswap_v2".to_string(),
2299 version: VersionParam {
2300 timestamp: Some(expected_timestamp),
2301 block: Some(BlockParam {
2302 hash: Some(block_hash),
2303 chain: Some(Chain::Ethereum),
2304 number: Some(block_number),
2305 }),
2306 },
2307 chain: Chain::Ethereum,
2308 include_balances: false,
2309 pagination: PaginationParams::default(),
2310 };
2311
2312 assert_eq!(result, expected);
2313 }
2314
2315 #[rstest]
2316 #[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()])]
2317 #[case::with_strings(vec!["id1".to_string(), "id2".to_string()], vec!["id1".to_string(), "id2".to_string()])]
2318 fn test_id_filtered<T>(#[case] input_ids: Vec<T>, #[case] expected_ids: Vec<String>)
2319 where
2320 T: Into<String> + Clone,
2321 {
2322 let request_body = ProtocolStateRequestBody::id_filtered(input_ids);
2323
2324 assert_eq!(request_body.protocol_ids, Some(expected_ids));
2325 }
2326
2327 fn create_models_block_changes() -> crate::models::blockchain::BlockAggregatedChanges {
2328 let base_ts = 1694534400; crate::models::blockchain::BlockAggregatedChanges {
2331 extractor: "native_name".to_string(),
2332 block: models::blockchain::Block::new(
2333 3,
2334 models::Chain::Ethereum,
2335 Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000003").unwrap(),
2336 Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000002").unwrap(),
2337 chrono::DateTime::from_timestamp(base_ts + 3000, 0).unwrap().naive_utc(),
2338 ),
2339 db_committed_block_height: Some(1),
2340 finalized_block_height: 1,
2341 revert: true,
2342 state_deltas: HashMap::from([
2343 ("pc_1".to_string(), models::protocol::ProtocolComponentStateDelta {
2344 component_id: "pc_1".to_string(),
2345 updated_attributes: HashMap::from([
2346 ("attr_2".to_string(), Bytes::from("0x0000000000000002")),
2347 ("attr_1".to_string(), Bytes::from("0x00000000000003e8")),
2348 ]),
2349 deleted_attributes: HashSet::new(),
2350 }),
2351 ]),
2352 new_protocol_components: HashMap::from([
2353 ("pc_2".to_string(), crate::models::protocol::ProtocolComponent {
2354 id: "pc_2".to_string(),
2355 protocol_system: "native_protocol_system".to_string(),
2356 protocol_type_name: "pt_1".to_string(),
2357 chain: models::Chain::Ethereum,
2358 tokens: vec![
2359 Bytes::from_str("0xdac17f958d2ee523a2206206994597c13d831ec7").unwrap(),
2360 Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
2361 ],
2362 contract_addresses: vec![],
2363 static_attributes: HashMap::new(),
2364 change: models::ChangeType::Creation,
2365 creation_tx: Bytes::from_str("0x000000000000000000000000000000000000000000000000000000000000c351").unwrap(),
2366 created_at: chrono::DateTime::from_timestamp(base_ts + 5000, 0).unwrap().naive_utc(),
2367 }),
2368 ]),
2369 deleted_protocol_components: HashMap::from([
2370 ("pc_3".to_string(), crate::models::protocol::ProtocolComponent {
2371 id: "pc_3".to_string(),
2372 protocol_system: "native_protocol_system".to_string(),
2373 protocol_type_name: "pt_2".to_string(),
2374 chain: models::Chain::Ethereum,
2375 tokens: vec![
2376 Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(),
2377 Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(),
2378 ],
2379 contract_addresses: vec![],
2380 static_attributes: HashMap::new(),
2381 change: models::ChangeType::Deletion,
2382 creation_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000009c41").unwrap(),
2383 created_at: chrono::DateTime::from_timestamp(base_ts + 4000, 0).unwrap().naive_utc(),
2384 }),
2385 ]),
2386 component_balances: HashMap::from([
2387 ("pc_1".to_string(), HashMap::from([
2388 (Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(), models::protocol::ComponentBalance {
2389 token: Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
2390 balance: Bytes::from("0x00000001"),
2391 balance_float: 1.0,
2392 modify_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
2393 component_id: "pc_1".to_string(),
2394 }),
2395 (Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(), models::protocol::ComponentBalance {
2396 token: Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(),
2397 balance: Bytes::from("0x000003e8"),
2398 balance_float: 1000.0,
2399 modify_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000007531").unwrap(),
2400 component_id: "pc_1".to_string(),
2401 }),
2402 ])),
2403 ]),
2404 account_balances: HashMap::from([
2405 (Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(), HashMap::from([
2406 (Bytes::from_str("0x7a250d5630b4cf539739df2c5dacb4c659f2488d").unwrap(), models::contract::AccountBalance {
2407 account: Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(),
2408 token: Bytes::from_str("0x7a250d5630b4cf539739df2c5dacb4c659f2488d").unwrap(),
2409 balance: Bytes::from("0x000003e8"),
2410 modify_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000007531").unwrap(),
2411 }),
2412 ])),
2413 ]),
2414 ..Default::default()
2415 }
2416 }
2417
2418 #[test]
2419 fn test_serialize_deserialize_block_changes() {
2420 let block_entity_changes = create_models_block_changes();
2425
2426 let json_data = serde_json::to_string(&block_entity_changes).expect("Failed to serialize");
2428
2429 serde_json::from_str::<BlockChanges>(&json_data).expect("parsing failed");
2431 }
2432
2433 #[test]
2434 fn test_parse_block_changes() {
2435 let json_data = r#"
2436 {
2437 "extractor": "vm:ambient",
2438 "chain": "ethereum",
2439 "block": {
2440 "number": 123,
2441 "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2442 "parent_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2443 "chain": "ethereum",
2444 "ts": "2023-09-14T00:00:00"
2445 },
2446 "finalized_block_height": 0,
2447 "revert": false,
2448 "new_tokens": {},
2449 "account_updates": {
2450 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2451 "address": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2452 "chain": "ethereum",
2453 "slots": {},
2454 "balance": "0x01f4",
2455 "code": "",
2456 "change": "Update"
2457 }
2458 },
2459 "state_updates": {
2460 "component_1": {
2461 "component_id": "component_1",
2462 "updated_attributes": {"attr1": "0x01"},
2463 "deleted_attributes": ["attr2"]
2464 }
2465 },
2466 "new_protocol_components":
2467 { "protocol_1": {
2468 "id": "protocol_1",
2469 "protocol_system": "system_1",
2470 "protocol_type_name": "type_1",
2471 "chain": "ethereum",
2472 "tokens": ["0x01", "0x02"],
2473 "contract_ids": ["0x01", "0x02"],
2474 "static_attributes": {"attr1": "0x01f4"},
2475 "change": "Update",
2476 "creation_tx": "0x01",
2477 "created_at": "2023-09-14T00:00:00"
2478 }
2479 },
2480 "deleted_protocol_components": {},
2481 "component_balances": {
2482 "protocol_1":
2483 {
2484 "0x01": {
2485 "token": "0x01",
2486 "balance": "0xb77831d23691653a01",
2487 "balance_float": 3.3844151001790677e21,
2488 "modify_tx": "0x01",
2489 "component_id": "protocol_1"
2490 }
2491 }
2492 },
2493 "account_balances": {
2494 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2495 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2496 "account": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2497 "token": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2498 "balance": "0x01f4",
2499 "modify_tx": "0x01"
2500 }
2501 }
2502 },
2503 "component_tvl": {
2504 "protocol_1": 1000.0
2505 },
2506 "dci_update": {
2507 "new_entrypoints": {
2508 "component_1": [
2509 {
2510 "external_id": "0x01:sig()",
2511 "target": "0x01",
2512 "signature": "sig()"
2513 }
2514 ]
2515 },
2516 "new_entrypoint_params": {
2517 "0x01:sig()": [
2518 [
2519 {
2520 "method": "rpctracer",
2521 "caller": "0x01",
2522 "calldata": "0x02"
2523 },
2524 "component_1"
2525 ]
2526 ]
2527 },
2528 "trace_results": {
2529 "0x01:sig()": {
2530 "retriggers": [
2531 ["0x01", {"key": "0x02", "offset": 12}]
2532 ],
2533 "accessed_slots": {
2534 "0x03": ["0x03", "0x04"]
2535 }
2536 }
2537 }
2538 }
2539 }
2540 "#;
2541
2542 serde_json::from_str::<BlockChanges>(json_data).expect("parsing failed");
2543 }
2544
2545 #[test]
2546 fn test_parse_websocket_message() {
2547 let json_data = r#"
2548 {
2549 "subscription_id": "5d23bfbe-89ad-4ea3-8672-dc9e973ac9dc",
2550 "deltas": {
2551 "type": "BlockChanges",
2552 "extractor": "uniswap_v2",
2553 "chain": "ethereum",
2554 "block": {
2555 "number": 19291517,
2556 "hash": "0xbc3ea4896c0be8da6229387a8571b72818aa258daf4fab46471003ad74c4ee83",
2557 "parent_hash": "0x89ca5b8d593574cf6c886f41ef8208bf6bdc1a90ef36046cb8c84bc880b9af8f",
2558 "chain": "ethereum",
2559 "ts": "2024-02-23T16:35:35"
2560 },
2561 "finalized_block_height": 0,
2562 "revert": false,
2563 "new_tokens": {},
2564 "account_updates": {
2565 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2566 "address": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2567 "chain": "ethereum",
2568 "slots": {},
2569 "balance": "0x01f4",
2570 "code": "",
2571 "change": "Update"
2572 }
2573 },
2574 "state_updates": {
2575 "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28": {
2576 "component_id": "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28",
2577 "updated_attributes": {
2578 "reserve0": "0x87f7b5973a7f28a8b32404",
2579 "reserve1": "0x09e9564b11"
2580 },
2581 "deleted_attributes": []
2582 },
2583 "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d": {
2584 "component_id": "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d",
2585 "updated_attributes": {
2586 "reserve1": "0x44d9a8fd662c2f4d03",
2587 "reserve0": "0x500b1261f811d5bf423e"
2588 },
2589 "deleted_attributes": []
2590 }
2591 },
2592 "new_protocol_components": {},
2593 "deleted_protocol_components": {},
2594 "component_balances": {
2595 "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d": {
2596 "0x9012744b7a564623b6c3e40b144fc196bdedf1a9": {
2597 "token": "0x9012744b7a564623b6c3e40b144fc196bdedf1a9",
2598 "balance": "0x500b1261f811d5bf423e",
2599 "balance_float": 3.779935574269033E23,
2600 "modify_tx": "0xe46c4db085fb6c6f3408a65524555797adb264e1d5cf3b66ad154598f85ac4bf",
2601 "component_id": "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d"
2602 },
2603 "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": {
2604 "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
2605 "balance": "0x44d9a8fd662c2f4d03",
2606 "balance_float": 1.270062661329837E21,
2607 "modify_tx": "0xe46c4db085fb6c6f3408a65524555797adb264e1d5cf3b66ad154598f85ac4bf",
2608 "component_id": "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d"
2609 }
2610 }
2611 },
2612 "account_balances": {
2613 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2614 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2615 "account": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2616 "token": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2617 "balance": "0x01f4",
2618 "modify_tx": "0x01"
2619 }
2620 }
2621 },
2622 "component_tvl": {},
2623 "dci_update": {
2624 "new_entrypoints": {
2625 "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28": [
2626 {
2627 "external_id": "0x01:sig()",
2628 "target": "0x01",
2629 "signature": "sig()"
2630 }
2631 ]
2632 },
2633 "new_entrypoint_params": {
2634 "0x01:sig()": [
2635 [
2636 {
2637 "method": "rpctracer",
2638 "caller": "0x01",
2639 "calldata": "0x02"
2640 },
2641 "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28"
2642 ]
2643 ]
2644 },
2645 "trace_results": {
2646 "0x01:sig()": {
2647 "retriggers": [
2648 ["0x01", {"key": "0x02", "offset": 12}]
2649 ],
2650 "accessed_slots": {
2651 "0x03": ["0x03", "0x04"]
2652 }
2653 }
2654 }
2655 }
2656 }
2657 }
2658 "#;
2659 serde_json::from_str::<WebSocketMessage>(json_data).expect("parsing failed");
2660 }
2661
2662 #[test]
2663 fn test_protocol_state_delta_merge_update_delete() {
2664 let mut delta1 = ProtocolStateDelta {
2666 component_id: "Component1".to_string(),
2667 updated_attributes: HashMap::from([(
2668 "Attribute1".to_string(),
2669 Bytes::from("0xbadbabe420"),
2670 )]),
2671 deleted_attributes: HashSet::new(),
2672 };
2673 let delta2 = ProtocolStateDelta {
2674 component_id: "Component1".to_string(),
2675 updated_attributes: HashMap::from([(
2676 "Attribute2".to_string(),
2677 Bytes::from("0x0badbabe"),
2678 )]),
2679 deleted_attributes: HashSet::from(["Attribute1".to_string()]),
2680 };
2681 let exp = ProtocolStateDelta {
2682 component_id: "Component1".to_string(),
2683 updated_attributes: HashMap::from([(
2684 "Attribute2".to_string(),
2685 Bytes::from("0x0badbabe"),
2686 )]),
2687 deleted_attributes: HashSet::from(["Attribute1".to_string()]),
2688 };
2689
2690 delta1.merge(&delta2);
2691
2692 assert_eq!(delta1, exp);
2693 }
2694
2695 #[test]
2696 fn test_protocol_state_delta_merge_delete_update() {
2697 let mut delta1 = ProtocolStateDelta {
2699 component_id: "Component1".to_string(),
2700 updated_attributes: HashMap::new(),
2701 deleted_attributes: HashSet::from(["Attribute1".to_string()]),
2702 };
2703 let delta2 = ProtocolStateDelta {
2704 component_id: "Component1".to_string(),
2705 updated_attributes: HashMap::from([(
2706 "Attribute1".to_string(),
2707 Bytes::from("0x0badbabe"),
2708 )]),
2709 deleted_attributes: HashSet::new(),
2710 };
2711 let exp = ProtocolStateDelta {
2712 component_id: "Component1".to_string(),
2713 updated_attributes: HashMap::from([(
2714 "Attribute1".to_string(),
2715 Bytes::from("0x0badbabe"),
2716 )]),
2717 deleted_attributes: HashSet::new(),
2718 };
2719
2720 delta1.merge(&delta2);
2721
2722 assert_eq!(delta1, exp);
2723 }
2724
2725 #[test]
2726 fn test_account_update_merge() {
2727 let mut account1 = AccountUpdate::new(
2729 Bytes::from(b"0x1234"),
2730 Chain::Ethereum,
2731 HashMap::from([(Bytes::from("0xaabb"), Bytes::from("0xccdd"))]),
2732 Some(Bytes::from("0x1000")),
2733 Some(Bytes::from("0xdeadbeaf")),
2734 ChangeType::Creation,
2735 );
2736
2737 let account2 = AccountUpdate::new(
2738 Bytes::from(b"0x1234"), Chain::Ethereum,
2740 HashMap::from([(Bytes::from("0xeeff"), Bytes::from("0x11223344"))]),
2741 Some(Bytes::from("0x2000")),
2742 Some(Bytes::from("0xcafebabe")),
2743 ChangeType::Update,
2744 );
2745
2746 account1.merge(&account2);
2748
2749 let expected = AccountUpdate::new(
2751 Bytes::from(b"0x1234"), Chain::Ethereum,
2753 HashMap::from([
2754 (Bytes::from("0xaabb"), Bytes::from("0xccdd")), (Bytes::from("0xeeff"), Bytes::from("0x11223344")), ]),
2757 Some(Bytes::from("0x2000")), Some(Bytes::from("0xcafebabe")), ChangeType::Creation, );
2761
2762 assert_eq!(account1, expected);
2764 }
2765
2766 #[test]
2767 fn test_block_account_changes_merge() {
2768 let old_account_updates: HashMap<Bytes, AccountUpdate> = [(
2770 Bytes::from("0x0011"),
2771 AccountUpdate {
2772 address: Bytes::from("0x00"),
2773 chain: Chain::Ethereum,
2774 slots: HashMap::from([(Bytes::from("0x0022"), Bytes::from("0x0033"))]),
2775 balance: Some(Bytes::from("0x01")),
2776 code: Some(Bytes::from("0x02")),
2777 change: ChangeType::Creation,
2778 },
2779 )]
2780 .into_iter()
2781 .collect();
2782 let new_account_updates: HashMap<Bytes, AccountUpdate> = [(
2783 Bytes::from("0x0011"),
2784 AccountUpdate {
2785 address: Bytes::from("0x00"),
2786 chain: Chain::Ethereum,
2787 slots: HashMap::from([(Bytes::from("0x0044"), Bytes::from("0x0055"))]),
2788 balance: Some(Bytes::from("0x03")),
2789 code: Some(Bytes::from("0x04")),
2790 change: ChangeType::Update,
2791 },
2792 )]
2793 .into_iter()
2794 .collect();
2795 let block_account_changes_initial = BlockChanges {
2797 extractor: "extractor1".to_string(),
2798 revert: false,
2799 account_updates: old_account_updates,
2800 ..Default::default()
2801 };
2802
2803 let block_account_changes_new = BlockChanges {
2804 extractor: "extractor2".to_string(),
2805 revert: true,
2806 account_updates: new_account_updates,
2807 ..Default::default()
2808 };
2809
2810 let res = block_account_changes_initial.merge(block_account_changes_new);
2812
2813 let expected_account_updates: HashMap<Bytes, AccountUpdate> = [(
2815 Bytes::from("0x0011"),
2816 AccountUpdate {
2817 address: Bytes::from("0x00"),
2818 chain: Chain::Ethereum,
2819 slots: HashMap::from([
2820 (Bytes::from("0x0044"), Bytes::from("0x0055")),
2821 (Bytes::from("0x0022"), Bytes::from("0x0033")),
2822 ]),
2823 balance: Some(Bytes::from("0x03")),
2824 code: Some(Bytes::from("0x04")),
2825 change: ChangeType::Creation,
2826 },
2827 )]
2828 .into_iter()
2829 .collect();
2830 let block_account_changes_expected = BlockChanges {
2831 extractor: "extractor1".to_string(),
2832 revert: true,
2833 account_updates: expected_account_updates,
2834 ..Default::default()
2835 };
2836 assert_eq!(res, block_account_changes_expected);
2837 }
2838
2839 #[test]
2840 fn test_block_entity_changes_merge() {
2841 let block_entity_changes_result1 = BlockChanges {
2843 extractor: String::from("extractor1"),
2844 revert: false,
2845 state_updates: hashmap! { "state1".to_string() => ProtocolStateDelta::default() },
2846 new_protocol_components: hashmap! { "component1".to_string() => ProtocolComponent::default() },
2847 deleted_protocol_components: HashMap::new(),
2848 component_balances: hashmap! {
2849 "component1".to_string() => TokenBalances(hashmap! {
2850 Bytes::from("0x01") => ComponentBalance {
2851 token: Bytes::from("0x01"),
2852 balance: Bytes::from("0x01"),
2853 balance_float: 1.0,
2854 modify_tx: Bytes::from("0x00"),
2855 component_id: "component1".to_string()
2856 },
2857 Bytes::from("0x02") => ComponentBalance {
2858 token: Bytes::from("0x02"),
2859 balance: Bytes::from("0x02"),
2860 balance_float: 2.0,
2861 modify_tx: Bytes::from("0x00"),
2862 component_id: "component1".to_string()
2863 },
2864 })
2865
2866 },
2867 component_tvl: hashmap! { "tvl1".to_string() => 1000.0 },
2868 ..Default::default()
2869 };
2870 let block_entity_changes_result2 = BlockChanges {
2871 extractor: String::from("extractor2"),
2872 revert: true,
2873 state_updates: hashmap! { "state2".to_string() => ProtocolStateDelta::default() },
2874 new_protocol_components: hashmap! { "component2".to_string() => ProtocolComponent::default() },
2875 deleted_protocol_components: hashmap! { "component3".to_string() => ProtocolComponent::default() },
2876 component_balances: hashmap! {
2877 "component1".to_string() => TokenBalances::default(),
2878 "component2".to_string() => TokenBalances::default()
2879 },
2880 component_tvl: hashmap! { "tvl2".to_string() => 2000.0 },
2881 ..Default::default()
2882 };
2883
2884 let res = block_entity_changes_result1.merge(block_entity_changes_result2);
2885
2886 let expected_block_entity_changes_result = BlockChanges {
2887 extractor: String::from("extractor1"),
2888 revert: true,
2889 state_updates: hashmap! {
2890 "state1".to_string() => ProtocolStateDelta::default(),
2891 "state2".to_string() => ProtocolStateDelta::default(),
2892 },
2893 new_protocol_components: hashmap! {
2894 "component1".to_string() => ProtocolComponent::default(),
2895 "component2".to_string() => ProtocolComponent::default(),
2896 },
2897 deleted_protocol_components: hashmap! {
2898 "component3".to_string() => ProtocolComponent::default(),
2899 },
2900 component_balances: hashmap! {
2901 "component1".to_string() => TokenBalances(hashmap! {
2902 Bytes::from("0x01") => ComponentBalance {
2903 token: Bytes::from("0x01"),
2904 balance: Bytes::from("0x01"),
2905 balance_float: 1.0,
2906 modify_tx: Bytes::from("0x00"),
2907 component_id: "component1".to_string()
2908 },
2909 Bytes::from("0x02") => ComponentBalance {
2910 token: Bytes::from("0x02"),
2911 balance: Bytes::from("0x02"),
2912 balance_float: 2.0,
2913 modify_tx: Bytes::from("0x00"),
2914 component_id: "component1".to_string()
2915 },
2916 }),
2917 "component2".to_string() => TokenBalances::default(),
2918 },
2919 component_tvl: hashmap! {
2920 "tvl1".to_string() => 1000.0,
2921 "tvl2".to_string() => 2000.0
2922 },
2923 ..Default::default()
2924 };
2925
2926 assert_eq!(res, expected_block_entity_changes_result);
2927 }
2928
2929 #[test]
2930 fn test_websocket_error_serialization() {
2931 let extractor_id = ExtractorIdentity::new(Chain::Ethereum, "test_extractor");
2932 let subscription_id = Uuid::new_v4();
2933
2934 let error = WebsocketError::ExtractorNotFound(extractor_id.clone());
2936 let json = serde_json::to_string(&error).unwrap();
2937 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
2938 assert_eq!(error, deserialized);
2939
2940 let error = WebsocketError::SubscriptionNotFound(subscription_id);
2942 let json = serde_json::to_string(&error).unwrap();
2943 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
2944 assert_eq!(error, deserialized);
2945
2946 let error = WebsocketError::ParseError("{asd".to_string(), "invalid json".to_string());
2948 let json = serde_json::to_string(&error).unwrap();
2949 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
2950 assert_eq!(error, deserialized);
2951
2952 let error = WebsocketError::SubscribeError(extractor_id.clone());
2954 let json = serde_json::to_string(&error).unwrap();
2955 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
2956 assert_eq!(error, deserialized);
2957 }
2958
2959 #[test]
2960 fn test_websocket_message_with_error_response() {
2961 let error =
2962 WebsocketError::ParseError("}asdfas".to_string(), "malformed request".to_string());
2963 let response = Response::Error(error.clone());
2964 let message = WebSocketMessage::Response(response);
2965
2966 let json = serde_json::to_string(&message).unwrap();
2967 let deserialized: WebSocketMessage = serde_json::from_str(&json).unwrap();
2968
2969 if let WebSocketMessage::Response(Response::Error(deserialized_error)) = deserialized {
2970 assert_eq!(error, deserialized_error);
2971 } else {
2972 panic!("Expected WebSocketMessage::Response(Response::Error)");
2973 }
2974 }
2975
2976 #[test]
2977 fn test_websocket_error_conversion_from_models() {
2978 use crate::models::error::WebsocketError as ModelsError;
2979
2980 let extractor_id =
2981 crate::models::ExtractorIdentity::new(crate::models::Chain::Ethereum, "test");
2982 let subscription_id = Uuid::new_v4();
2983
2984 let models_error = ModelsError::ExtractorNotFound(extractor_id.clone());
2986 let dto_error: WebsocketError = models_error.into();
2987 assert_eq!(dto_error, WebsocketError::ExtractorNotFound(extractor_id.clone().into()));
2988
2989 let models_error = ModelsError::SubscriptionNotFound(subscription_id);
2991 let dto_error: WebsocketError = models_error.into();
2992 assert_eq!(dto_error, WebsocketError::SubscriptionNotFound(subscription_id));
2993
2994 let json_result: Result<serde_json::Value, _> = serde_json::from_str("{invalid json");
2996 let json_error = json_result.unwrap_err();
2997 let models_error = ModelsError::ParseError("{invalid json".to_string(), json_error);
2998 let dto_error: WebsocketError = models_error.into();
2999 if let WebsocketError::ParseError(msg, error) = dto_error {
3000 assert!(!error.is_empty(), "Error message should not be empty, got: '{}'", msg);
3002 } else {
3003 panic!("Expected ParseError variant");
3004 }
3005
3006 let models_error = ModelsError::SubscribeError(extractor_id.clone());
3008 let dto_error: WebsocketError = models_error.into();
3009 assert_eq!(dto_error, WebsocketError::SubscribeError(extractor_id.into()));
3010 }
3011}
3012
3013#[cfg(test)]
3014mod memory_size_tests {
3015 use std::collections::HashMap;
3016
3017 use super::*;
3018
3019 #[test]
3020 fn test_state_request_response_memory_size_empty() {
3021 let response = StateRequestResponse {
3022 accounts: vec![],
3023 pagination: PaginationResponse::new(1, 10, 0),
3024 };
3025
3026 let size = response.deep_size_of();
3027
3028 assert!(size >= 48, "Empty response should have minimum size of 48 bytes, got {}", size);
3030 assert!(size < 200, "Empty response should not be too large, got {}", size);
3031 }
3032
3033 #[test]
3034 fn test_state_request_response_memory_size_scales_with_slots() {
3035 let create_response_with_slots = |slot_count: usize| {
3036 let mut slots = HashMap::new();
3037 for i in 0..slot_count {
3038 let key = vec![i as u8; 32]; let value = vec![(i + 100) as u8; 32]; slots.insert(key.into(), value.into());
3041 }
3042
3043 let account = ResponseAccount::new(
3044 Chain::Ethereum,
3045 vec![1; 20].into(),
3046 "Pool".to_string(),
3047 slots,
3048 vec![1; 32].into(),
3049 HashMap::new(),
3050 vec![].into(), vec![1; 32].into(),
3052 vec![1; 32].into(),
3053 vec![1; 32].into(),
3054 None,
3055 );
3056
3057 StateRequestResponse {
3058 accounts: vec![account],
3059 pagination: PaginationResponse::new(1, 10, 1),
3060 }
3061 };
3062
3063 let small_response = create_response_with_slots(10);
3064 let large_response = create_response_with_slots(100);
3065
3066 let small_size = small_response.deep_size_of();
3067 let large_size = large_response.deep_size_of();
3068
3069 assert!(
3071 large_size > small_size * 5,
3072 "Large response ({} bytes) should be much larger than small response ({} bytes)",
3073 large_size,
3074 small_size
3075 );
3076
3077 let size_diff = large_size - small_size;
3079 let expected_min_diff = 90 * 64; assert!(
3081 size_diff > expected_min_diff,
3082 "Size difference ({} bytes) should reflect the additional slot data",
3083 size_diff
3084 );
3085 }
3086}