1#![allow(deprecated)]
8use std::{
9 collections::{BTreeMap, HashMap, HashSet},
10 fmt,
11 hash::{Hash, Hasher},
12};
13
14use chrono::{NaiveDateTime, Utc};
15use serde::{de, Deserialize, Deserializer, Serialize};
16use strum_macros::{Display, EnumString};
17use thiserror::Error;
18use utoipa::{IntoParams, ToSchema};
19use uuid::Uuid;
20
21use crate::{
22 models::{
23 self, blockchain::BlockAggregatedChanges, Address, Balance, Code, ComponentId, StoreKey,
24 StoreVal,
25 },
26 serde_primitives::{
27 hex_bytes, hex_bytes_option, hex_hashmap_key, hex_hashmap_key_value, hex_hashmap_value,
28 },
29 traits::MemorySize,
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)]
48#[serde(rename_all = "lowercase")]
49#[strum(serialize_all = "lowercase")]
50pub enum Chain {
51 #[default]
52 Ethereum,
53 Starknet,
54 ZkSync,
55 Arbitrum,
56 Base,
57 Unichain,
58}
59
60impl From<models::contract::Account> for ResponseAccount {
61 fn from(value: models::contract::Account) -> Self {
62 ResponseAccount::new(
63 value.chain.into(),
64 value.address,
65 value.title,
66 value.slots,
67 value.native_balance,
68 value
69 .token_balances
70 .into_iter()
71 .map(|(k, v)| (k, v.balance))
72 .collect(),
73 value.code,
74 value.code_hash,
75 value.balance_modify_tx,
76 value.code_modify_tx,
77 value.creation_tx,
78 )
79 }
80}
81
82impl From<models::Chain> for Chain {
83 fn from(value: models::Chain) -> Self {
84 match value {
85 models::Chain::Ethereum => Chain::Ethereum,
86 models::Chain::Starknet => Chain::Starknet,
87 models::Chain::ZkSync => Chain::ZkSync,
88 models::Chain::Arbitrum => Chain::Arbitrum,
89 models::Chain::Base => Chain::Base,
90 models::Chain::Unichain => Chain::Unichain,
91 }
92 }
93}
94
95#[derive(
96 Debug, PartialEq, Default, Copy, Clone, Deserialize, Serialize, ToSchema, EnumString, Display,
97)]
98pub enum ChangeType {
99 #[default]
100 Update,
101 Deletion,
102 Creation,
103 Unspecified,
104}
105
106impl From<models::ChangeType> for ChangeType {
107 fn from(value: models::ChangeType) -> Self {
108 match value {
109 models::ChangeType::Update => ChangeType::Update,
110 models::ChangeType::Creation => ChangeType::Creation,
111 models::ChangeType::Deletion => ChangeType::Deletion,
112 }
113 }
114}
115
116impl ChangeType {
117 pub fn merge(&self, other: &Self) -> Self {
118 if matches!(self, Self::Creation) {
119 Self::Creation
120 } else {
121 *other
122 }
123 }
124}
125
126#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Default)]
127pub struct ExtractorIdentity {
128 pub chain: Chain,
129 pub name: String,
130}
131
132impl ExtractorIdentity {
133 pub fn new(chain: Chain, name: &str) -> Self {
134 Self { chain, name: name.to_owned() }
135 }
136}
137
138impl fmt::Display for ExtractorIdentity {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 write!(f, "{}:{}", self.chain, self.name)
141 }
142}
143
144#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
146#[serde(tag = "method", rename_all = "lowercase")]
147pub enum Command {
148 Subscribe { extractor_id: ExtractorIdentity, include_state: bool },
149 Unsubscribe { subscription_id: Uuid },
150}
151
152#[derive(Error, Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
160pub enum WebsocketError {
161 #[error("Extractor not found: {0}")]
162 ExtractorNotFound(ExtractorIdentity),
163
164 #[error("Subscription not found: {0}")]
165 SubscriptionNotFound(Uuid),
166
167 #[error("Failed to parse JSON: {1}, msg: {0}")]
168 ParseError(String, String),
169
170 #[error("Failed to subscribe to extractor: {0}")]
171 SubscribeError(ExtractorIdentity),
172}
173
174impl From<crate::models::error::WebsocketError> for WebsocketError {
175 fn from(value: crate::models::error::WebsocketError) -> Self {
176 match value {
177 crate::models::error::WebsocketError::ExtractorNotFound(eid) => {
178 Self::ExtractorNotFound(eid.into())
179 }
180 crate::models::error::WebsocketError::SubscriptionNotFound(sid) => {
181 Self::SubscriptionNotFound(sid)
182 }
183 crate::models::error::WebsocketError::ParseError(raw, error) => {
184 Self::ParseError(error.to_string(), raw)
185 }
186 crate::models::error::WebsocketError::SubscribeError(eid) => {
187 Self::SubscribeError(eid.into())
188 }
189 }
190 }
191}
192
193#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
195#[serde(tag = "method", rename_all = "lowercase")]
196pub enum Response {
197 NewSubscription { extractor_id: ExtractorIdentity, subscription_id: Uuid },
198 SubscriptionEnded { subscription_id: Uuid },
199 Error(WebsocketError),
200}
201
202#[allow(clippy::large_enum_variant)]
204#[derive(Serialize, Deserialize, Debug, Display, Clone)]
205#[serde(untagged)]
206pub enum WebSocketMessage {
207 BlockChanges { subscription_id: Uuid, deltas: BlockChanges },
208 Response(Response),
209}
210
211#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default, ToSchema)]
212pub struct Block {
213 pub number: u64,
214 #[serde(with = "hex_bytes")]
215 pub hash: Bytes,
216 #[serde(with = "hex_bytes")]
217 pub parent_hash: Bytes,
218 pub chain: Chain,
219 pub ts: NaiveDateTime,
220}
221
222#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema, Eq, Hash)]
223#[serde(deny_unknown_fields)]
224pub struct BlockParam {
225 #[schema(value_type=Option<String>)]
226 #[serde(with = "hex_bytes_option", default)]
227 pub hash: Option<Bytes>,
228 #[deprecated(
229 note = "The `chain` field is deprecated and will be removed in a future version."
230 )]
231 #[serde(default)]
232 pub chain: Option<Chain>,
233 #[serde(default)]
234 pub number: Option<i64>,
235}
236
237impl From<&Block> for BlockParam {
238 fn from(value: &Block) -> Self {
239 BlockParam { hash: Some(value.hash.clone()), chain: None, number: None }
241 }
242}
243
244#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
245pub struct TokenBalances(#[serde(with = "hex_hashmap_key")] pub HashMap<Bytes, ComponentBalance>);
246
247impl From<HashMap<Bytes, ComponentBalance>> for TokenBalances {
248 fn from(value: HashMap<Bytes, ComponentBalance>) -> Self {
249 TokenBalances(value)
250 }
251}
252
253#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
254pub struct Transaction {
255 #[serde(with = "hex_bytes")]
256 pub hash: Bytes,
257 #[serde(with = "hex_bytes")]
258 pub block_hash: Bytes,
259 #[serde(with = "hex_bytes")]
260 pub from: Bytes,
261 #[serde(with = "hex_bytes_option")]
262 pub to: Option<Bytes>,
263 pub index: u64,
264}
265
266impl Transaction {
267 pub fn new(hash: Bytes, block_hash: Bytes, from: Bytes, to: Option<Bytes>, index: u64) -> Self {
268 Self { hash, block_hash, from, to, index }
269 }
270}
271
272#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
274pub struct BlockChanges {
275 pub extractor: String,
276 pub chain: Chain,
277 pub block: Block,
278 pub finalized_block_height: u64,
279 pub revert: bool,
280 #[serde(with = "hex_hashmap_key", default)]
281 pub new_tokens: HashMap<Bytes, ResponseToken>,
282 #[serde(alias = "account_deltas", with = "hex_hashmap_key")]
283 pub account_updates: HashMap<Bytes, AccountUpdate>,
284 #[serde(alias = "state_deltas")]
285 pub state_updates: HashMap<String, ProtocolStateDelta>,
286 pub new_protocol_components: HashMap<String, ProtocolComponent>,
287 pub deleted_protocol_components: HashMap<String, ProtocolComponent>,
288 pub component_balances: HashMap<String, TokenBalances>,
289 pub account_balances: HashMap<Bytes, HashMap<Bytes, AccountBalance>>,
290 pub component_tvl: HashMap<String, f64>,
291 pub dci_update: DCIUpdate,
292}
293
294impl BlockChanges {
295 #[allow(clippy::too_many_arguments)]
296 pub fn new(
297 extractor: &str,
298 chain: Chain,
299 block: Block,
300 finalized_block_height: u64,
301 revert: bool,
302 account_updates: HashMap<Bytes, AccountUpdate>,
303 state_updates: HashMap<String, ProtocolStateDelta>,
304 new_protocol_components: HashMap<String, ProtocolComponent>,
305 deleted_protocol_components: HashMap<String, ProtocolComponent>,
306 component_balances: HashMap<String, HashMap<Bytes, ComponentBalance>>,
307 account_balances: HashMap<Bytes, HashMap<Bytes, AccountBalance>>,
308 dci_update: DCIUpdate,
309 ) -> Self {
310 BlockChanges {
311 extractor: extractor.to_owned(),
312 chain,
313 block,
314 finalized_block_height,
315 revert,
316 new_tokens: HashMap::new(),
317 account_updates,
318 state_updates,
319 new_protocol_components,
320 deleted_protocol_components,
321 component_balances: component_balances
322 .into_iter()
323 .map(|(k, v)| (k, v.into()))
324 .collect(),
325 account_balances,
326 component_tvl: HashMap::new(),
327 dci_update,
328 }
329 }
330
331 pub fn merge(mut self, other: Self) -> Self {
332 other
333 .account_updates
334 .into_iter()
335 .for_each(|(k, v)| {
336 self.account_updates
337 .entry(k)
338 .and_modify(|e| {
339 e.merge(&v);
340 })
341 .or_insert(v);
342 });
343
344 other
345 .state_updates
346 .into_iter()
347 .for_each(|(k, v)| {
348 self.state_updates
349 .entry(k)
350 .and_modify(|e| {
351 e.merge(&v);
352 })
353 .or_insert(v);
354 });
355
356 other
357 .component_balances
358 .into_iter()
359 .for_each(|(k, v)| {
360 self.component_balances
361 .entry(k)
362 .and_modify(|e| e.0.extend(v.0.clone()))
363 .or_insert_with(|| v);
364 });
365
366 other
367 .account_balances
368 .into_iter()
369 .for_each(|(k, v)| {
370 self.account_balances
371 .entry(k)
372 .and_modify(|e| e.extend(v.clone()))
373 .or_insert(v);
374 });
375
376 self.component_tvl
377 .extend(other.component_tvl);
378 self.new_protocol_components
379 .extend(other.new_protocol_components);
380 self.deleted_protocol_components
381 .extend(other.deleted_protocol_components);
382 self.revert = other.revert;
383 self.block = other.block;
384
385 self
386 }
387
388 pub fn get_block(&self) -> &Block {
389 &self.block
390 }
391
392 pub fn is_revert(&self) -> bool {
393 self.revert
394 }
395
396 pub fn filter_by_component<F: Fn(&str) -> bool>(&mut self, keep: F) {
397 self.state_updates
398 .retain(|k, _| keep(k));
399 self.component_balances
400 .retain(|k, _| keep(k));
401 self.component_tvl
402 .retain(|k, _| keep(k));
403 }
404
405 pub fn filter_by_contract<F: Fn(&Bytes) -> bool>(&mut self, keep: F) {
406 self.account_updates
407 .retain(|k, _| keep(k));
408 self.account_balances
409 .retain(|k, _| keep(k));
410 }
411
412 pub fn n_changes(&self) -> usize {
413 self.account_updates.len() + self.state_updates.len()
414 }
415
416 pub fn drop_state(&self) -> Self {
417 Self {
418 extractor: self.extractor.clone(),
419 chain: self.chain,
420 block: self.block.clone(),
421 finalized_block_height: self.finalized_block_height,
422 revert: self.revert,
423 new_tokens: self.new_tokens.clone(),
424 account_updates: HashMap::new(),
425 state_updates: HashMap::new(),
426 new_protocol_components: self.new_protocol_components.clone(),
427 deleted_protocol_components: self.deleted_protocol_components.clone(),
428 component_balances: self.component_balances.clone(),
429 account_balances: self.account_balances.clone(),
430 component_tvl: self.component_tvl.clone(),
431 dci_update: self.dci_update.clone(),
432 }
433 }
434}
435
436impl From<models::blockchain::Block> for Block {
437 fn from(value: models::blockchain::Block) -> Self {
438 Self {
439 number: value.number,
440 hash: value.hash,
441 parent_hash: value.parent_hash,
442 chain: value.chain.into(),
443 ts: value.ts,
444 }
445 }
446}
447
448impl From<models::protocol::ComponentBalance> for ComponentBalance {
449 fn from(value: models::protocol::ComponentBalance) -> Self {
450 Self {
451 token: value.token,
452 balance: value.balance,
453 balance_float: value.balance_float,
454 modify_tx: value.modify_tx,
455 component_id: value.component_id,
456 }
457 }
458}
459
460impl From<models::contract::AccountBalance> for AccountBalance {
461 fn from(value: models::contract::AccountBalance) -> Self {
462 Self {
463 account: value.account,
464 token: value.token,
465 balance: value.balance,
466 modify_tx: value.modify_tx,
467 }
468 }
469}
470
471impl From<BlockAggregatedChanges> for BlockChanges {
472 fn from(value: BlockAggregatedChanges) -> Self {
473 Self {
474 extractor: value.extractor,
475 chain: value.chain.into(),
476 block: value.block.into(),
477 finalized_block_height: value.finalized_block_height,
478 revert: value.revert,
479 account_updates: value
480 .account_deltas
481 .into_iter()
482 .map(|(k, v)| (k, v.into()))
483 .collect(),
484 state_updates: value
485 .state_deltas
486 .into_iter()
487 .map(|(k, v)| (k, v.into()))
488 .collect(),
489 new_protocol_components: value
490 .new_protocol_components
491 .into_iter()
492 .map(|(k, v)| (k, v.into()))
493 .collect(),
494 deleted_protocol_components: value
495 .deleted_protocol_components
496 .into_iter()
497 .map(|(k, v)| (k, v.into()))
498 .collect(),
499 component_balances: value
500 .component_balances
501 .into_iter()
502 .map(|(component_id, v)| {
503 let balances: HashMap<Bytes, ComponentBalance> = v
504 .into_iter()
505 .map(|(k, v)| (k, ComponentBalance::from(v)))
506 .collect();
507 (component_id, balances.into())
508 })
509 .collect(),
510 account_balances: value
511 .account_balances
512 .into_iter()
513 .map(|(k, v)| {
514 (
515 k,
516 v.into_iter()
517 .map(|(k, v)| (k, v.into()))
518 .collect(),
519 )
520 })
521 .collect(),
522 dci_update: value.dci_update.into(),
523 new_tokens: value
524 .new_tokens
525 .into_iter()
526 .map(|(k, v)| (k, v.into()))
527 .collect(),
528 component_tvl: value.component_tvl,
529 }
530 }
531}
532
533#[derive(PartialEq, Serialize, Deserialize, Clone, Debug, ToSchema)]
534pub struct AccountUpdate {
535 #[serde(with = "hex_bytes")]
536 #[schema(value_type=Vec<String>)]
537 pub address: Bytes,
538 pub chain: Chain,
539 #[serde(with = "hex_hashmap_key_value")]
540 #[schema(value_type=HashMap<String, String>)]
541 pub slots: HashMap<Bytes, Bytes>,
542 #[serde(with = "hex_bytes_option")]
543 #[schema(value_type=Option<String>)]
544 pub balance: Option<Bytes>,
545 #[serde(with = "hex_bytes_option")]
546 #[schema(value_type=Option<String>)]
547 pub code: Option<Bytes>,
548 pub change: ChangeType,
549}
550
551impl AccountUpdate {
552 pub fn new(
553 address: Bytes,
554 chain: Chain,
555 slots: HashMap<Bytes, Bytes>,
556 balance: Option<Bytes>,
557 code: Option<Bytes>,
558 change: ChangeType,
559 ) -> Self {
560 Self { address, chain, slots, balance, code, change }
561 }
562
563 pub fn merge(&mut self, other: &Self) {
564 self.slots.extend(
565 other
566 .slots
567 .iter()
568 .map(|(k, v)| (k.clone(), v.clone())),
569 );
570 self.balance.clone_from(&other.balance);
571 self.code.clone_from(&other.code);
572 self.change = self.change.merge(&other.change);
573 }
574}
575
576impl From<models::contract::AccountDelta> for AccountUpdate {
577 fn from(value: models::contract::AccountDelta) -> Self {
578 let code = value.code().clone();
579 let change_type = value.change_type().into();
580 AccountUpdate::new(
581 value.address,
582 value.chain.into(),
583 value
584 .slots
585 .into_iter()
586 .map(|(k, v)| (k, v.unwrap_or_default()))
587 .collect(),
588 value.balance,
589 code,
590 change_type,
591 )
592 }
593}
594
595#[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize, ToSchema)]
597pub struct ProtocolComponent {
598 pub id: String,
600 pub protocol_system: String,
602 pub protocol_type_name: String,
604 pub chain: Chain,
605 #[schema(value_type=Vec<String>)]
607 pub tokens: Vec<Bytes>,
608 #[serde(alias = "contract_addresses")]
611 #[schema(value_type=Vec<String>)]
612 pub contract_ids: Vec<Bytes>,
613 #[serde(with = "hex_hashmap_value")]
615 #[schema(value_type=HashMap<String, String>)]
616 pub static_attributes: HashMap<String, Bytes>,
617 #[serde(default)]
619 pub change: ChangeType,
620 #[serde(with = "hex_bytes")]
622 #[schema(value_type=String)]
623 pub creation_tx: Bytes,
624 pub created_at: NaiveDateTime,
626}
627
628impl From<models::protocol::ProtocolComponent> for ProtocolComponent {
629 fn from(value: models::protocol::ProtocolComponent) -> Self {
630 Self {
631 id: value.id,
632 protocol_system: value.protocol_system,
633 protocol_type_name: value.protocol_type_name,
634 chain: value.chain.into(),
635 tokens: value.tokens,
636 contract_ids: value.contract_addresses,
637 static_attributes: value.static_attributes,
638 change: value.change.into(),
639 creation_tx: value.creation_tx,
640 created_at: value.created_at,
641 }
642 }
643}
644
645#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
646pub struct ComponentBalance {
647 #[serde(with = "hex_bytes")]
648 pub token: Bytes,
649 pub balance: Bytes,
650 pub balance_float: f64,
651 #[serde(with = "hex_bytes")]
652 pub modify_tx: Bytes,
653 pub component_id: String,
654}
655
656#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, ToSchema)]
657pub struct ProtocolStateDelta {
659 pub component_id: String,
660 #[schema(value_type=HashMap<String, String>)]
661 pub updated_attributes: HashMap<String, Bytes>,
662 pub deleted_attributes: HashSet<String>,
663}
664
665impl From<models::protocol::ProtocolComponentStateDelta> for ProtocolStateDelta {
666 fn from(value: models::protocol::ProtocolComponentStateDelta) -> Self {
667 Self {
668 component_id: value.component_id,
669 updated_attributes: value.updated_attributes,
670 deleted_attributes: value.deleted_attributes,
671 }
672 }
673}
674
675impl ProtocolStateDelta {
676 pub fn merge(&mut self, other: &Self) {
695 self.updated_attributes
697 .retain(|k, _| !other.deleted_attributes.contains(k));
698
699 self.deleted_attributes.retain(|attr| {
701 !other
702 .updated_attributes
703 .contains_key(attr)
704 });
705
706 self.updated_attributes.extend(
708 other
709 .updated_attributes
710 .iter()
711 .map(|(k, v)| (k.clone(), v.clone())),
712 );
713
714 self.deleted_attributes
716 .extend(other.deleted_attributes.iter().cloned());
717 }
718}
719
720#[derive(Clone, Serialize, Debug, Default, Deserialize, PartialEq, ToSchema, Eq, Hash)]
722#[serde(deny_unknown_fields)]
723pub struct StateRequestBody {
724 #[serde(alias = "contractIds")]
726 #[schema(value_type=Option<Vec<String>>)]
727 pub contract_ids: Option<Vec<Bytes>>,
728 #[serde(alias = "protocolSystem", default)]
731 pub protocol_system: String,
732 #[serde(default = "VersionParam::default")]
733 pub version: VersionParam,
734 #[serde(default)]
735 pub chain: Chain,
736 #[serde(default)]
737 pub pagination: PaginationParams,
738}
739
740impl StateRequestBody {
741 pub fn new(
742 contract_ids: Option<Vec<Bytes>>,
743 protocol_system: String,
744 version: VersionParam,
745 chain: Chain,
746 pagination: PaginationParams,
747 ) -> Self {
748 Self { contract_ids, protocol_system, version, chain, pagination }
749 }
750
751 pub fn from_block(protocol_system: &str, block: BlockParam) -> Self {
752 Self {
753 contract_ids: None,
754 protocol_system: protocol_system.to_string(),
755 version: VersionParam { timestamp: None, block: Some(block.clone()) },
756 chain: block.chain.unwrap_or_default(),
757 pagination: PaginationParams::default(),
758 }
759 }
760
761 pub fn from_timestamp(protocol_system: &str, timestamp: NaiveDateTime, chain: Chain) -> Self {
762 Self {
763 contract_ids: None,
764 protocol_system: protocol_system.to_string(),
765 version: VersionParam { timestamp: Some(timestamp), block: None },
766 chain,
767 pagination: PaginationParams::default(),
768 }
769 }
770}
771
772#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema)]
774pub struct StateRequestResponse {
775 pub accounts: Vec<ResponseAccount>,
776 pub pagination: PaginationResponse,
777}
778
779impl StateRequestResponse {
780 pub fn new(accounts: Vec<ResponseAccount>, pagination: PaginationResponse) -> Self {
781 Self { accounts, pagination }
782 }
783}
784
785impl MemorySize for StateRequestResponse {
786 fn memory_size(&self) -> usize {
787 let mut size = 0usize;
788
789 size += std::mem::size_of::<Vec<ResponseAccount>>();
791 size += std::mem::size_of::<PaginationResponse>();
792
793 for account in &self.accounts {
795 size += 200; size += account.address.len();
800 size += account.title.capacity(); size += account.native_balance.len();
802 size += account.code.len();
803 size += account.code_hash.len();
804 size += account.balance_modify_tx.len();
805 size += account.code_modify_tx.len();
806
807 if let Some(ref creation_tx) = account.creation_tx {
809 size += creation_tx.len();
810 }
811
812 size += account.slots.capacity() * 64; for (key, value) in &account.slots {
815 size += key.len(); size += value.len();
818 }
819
820 size += account.token_balances.capacity() * 64; for (key, value) in &account.token_balances {
823 size += key.len();
825 size += value.len();
826 }
827 }
828
829 size.max(128)
831 }
832}
833
834#[derive(PartialEq, Clone, Serialize, Deserialize, Default, ToSchema)]
835#[serde(rename = "Account")]
836pub struct ResponseAccount {
840 pub chain: Chain,
841 #[schema(value_type=String, example="0xc9f2e6ea1637E499406986ac50ddC92401ce1f58")]
843 #[serde(with = "hex_bytes")]
844 pub address: Bytes,
845 #[schema(value_type=String, example="Protocol Vault")]
847 pub title: String,
848 #[schema(value_type=HashMap<String, String>, example=json!({"0x....": "0x...."}))]
850 #[serde(with = "hex_hashmap_key_value")]
851 pub slots: HashMap<Bytes, Bytes>,
852 #[schema(value_type=String, example="0x00")]
854 #[serde(with = "hex_bytes")]
855 pub native_balance: Bytes,
856 #[schema(value_type=HashMap<String, String>, example=json!({"0x....": "0x...."}))]
859 #[serde(with = "hex_hashmap_key_value")]
860 pub token_balances: HashMap<Bytes, Bytes>,
861 #[schema(value_type=String, example="0xBADBABE")]
863 #[serde(with = "hex_bytes")]
864 pub code: Bytes,
865 #[schema(value_type=String, example="0x123456789")]
867 #[serde(with = "hex_bytes")]
868 pub code_hash: Bytes,
869 #[schema(value_type=String, example="0x8f1133bfb054a23aedfe5d25b1d81b96195396d8b88bd5d4bcf865fc1ae2c3f4")]
871 #[serde(with = "hex_bytes")]
872 pub balance_modify_tx: Bytes,
873 #[schema(value_type=String, example="0x8f1133bfb054a23aedfe5d25b1d81b96195396d8b88bd5d4bcf865fc1ae2c3f4")]
875 #[serde(with = "hex_bytes")]
876 pub code_modify_tx: Bytes,
877 #[deprecated(note = "The `creation_tx` field is deprecated.")]
879 #[schema(value_type=Option<String>, example="0x8f1133bfb054a23aedfe5d25b1d81b96195396d8b88bd5d4bcf865fc1ae2c3f4")]
880 #[serde(with = "hex_bytes_option")]
881 pub creation_tx: Option<Bytes>,
882}
883
884impl ResponseAccount {
885 #[allow(clippy::too_many_arguments)]
886 pub fn new(
887 chain: Chain,
888 address: Bytes,
889 title: String,
890 slots: HashMap<Bytes, Bytes>,
891 native_balance: Bytes,
892 token_balances: HashMap<Bytes, Bytes>,
893 code: Bytes,
894 code_hash: Bytes,
895 balance_modify_tx: Bytes,
896 code_modify_tx: Bytes,
897 creation_tx: Option<Bytes>,
898 ) -> Self {
899 Self {
900 chain,
901 address,
902 title,
903 slots,
904 native_balance,
905 token_balances,
906 code,
907 code_hash,
908 balance_modify_tx,
909 code_modify_tx,
910 creation_tx,
911 }
912 }
913}
914
915impl fmt::Debug for ResponseAccount {
917 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
918 f.debug_struct("ResponseAccount")
919 .field("chain", &self.chain)
920 .field("address", &self.address)
921 .field("title", &self.title)
922 .field("slots", &self.slots)
923 .field("native_balance", &self.native_balance)
924 .field("token_balances", &self.token_balances)
925 .field("code", &format!("[{} bytes]", self.code.len()))
926 .field("code_hash", &self.code_hash)
927 .field("balance_modify_tx", &self.balance_modify_tx)
928 .field("code_modify_tx", &self.code_modify_tx)
929 .field("creation_tx", &self.creation_tx)
930 .finish()
931 }
932}
933
934#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
935pub struct AccountBalance {
936 #[serde(with = "hex_bytes")]
937 pub account: Bytes,
938 #[serde(with = "hex_bytes")]
939 pub token: Bytes,
940 #[serde(with = "hex_bytes")]
941 pub balance: Bytes,
942 #[serde(with = "hex_bytes")]
943 pub modify_tx: Bytes,
944}
945
946#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
947#[serde(deny_unknown_fields)]
948pub struct ContractId {
949 #[serde(with = "hex_bytes")]
950 #[schema(value_type=String)]
951 pub address: Bytes,
952 pub chain: Chain,
953}
954
955impl ContractId {
957 pub fn new(chain: Chain, address: Bytes) -> Self {
958 Self { address, chain }
959 }
960
961 pub fn address(&self) -> &Bytes {
962 &self.address
963 }
964}
965
966impl fmt::Display for ContractId {
967 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
968 write!(f, "{:?}: 0x{}", self.chain, hex::encode(&self.address))
969 }
970}
971
972#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema, Eq, Hash)]
979#[serde(deny_unknown_fields)]
980pub struct VersionParam {
981 pub timestamp: Option<NaiveDateTime>,
982 pub block: Option<BlockParam>,
983}
984
985impl VersionParam {
986 pub fn new(timestamp: Option<NaiveDateTime>, block: Option<BlockParam>) -> Self {
987 Self { timestamp, block }
988 }
989}
990
991impl Default for VersionParam {
992 fn default() -> Self {
993 VersionParam { timestamp: Some(Utc::now().naive_utc()), block: None }
994 }
995}
996
997#[deprecated(note = "Use StateRequestBody instead")]
998#[derive(Serialize, Deserialize, Default, Debug, IntoParams)]
999pub struct StateRequestParameters {
1000 #[param(default = 0)]
1002 pub tvl_gt: Option<u64>,
1003 #[param(default = 0)]
1005 pub inertia_min_gt: Option<u64>,
1006 #[serde(default = "default_include_balances_flag")]
1008 pub include_balances: bool,
1009 #[serde(default)]
1010 pub pagination: PaginationParams,
1011}
1012
1013impl StateRequestParameters {
1014 pub fn new(include_balances: bool) -> Self {
1015 Self {
1016 tvl_gt: None,
1017 inertia_min_gt: None,
1018 include_balances,
1019 pagination: PaginationParams::default(),
1020 }
1021 }
1022
1023 pub fn to_query_string(&self) -> String {
1024 let mut parts = vec![format!("include_balances={}", self.include_balances)];
1025
1026 if let Some(tvl_gt) = self.tvl_gt {
1027 parts.push(format!("tvl_gt={tvl_gt}"));
1028 }
1029
1030 if let Some(inertia) = self.inertia_min_gt {
1031 parts.push(format!("inertia_min_gt={inertia}"));
1032 }
1033
1034 let mut res = parts.join("&");
1035 if !res.is_empty() {
1036 res = format!("?{res}");
1037 }
1038 res
1039 }
1040}
1041
1042#[derive(Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Hash, Clone)]
1043#[serde(deny_unknown_fields)]
1044pub struct TokensRequestBody {
1045 #[serde(alias = "tokenAddresses")]
1047 #[schema(value_type=Option<Vec<String>>)]
1048 pub token_addresses: Option<Vec<Bytes>>,
1049 #[serde(default)]
1057 pub min_quality: Option<i32>,
1058 #[serde(default)]
1060 pub traded_n_days_ago: Option<u64>,
1061 #[serde(default)]
1063 pub pagination: PaginationParams,
1064 #[serde(default)]
1066 pub chain: Chain,
1067}
1068
1069#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, Eq, Hash)]
1071pub struct TokensRequestResponse {
1072 pub tokens: Vec<ResponseToken>,
1073 pub pagination: PaginationResponse,
1074}
1075
1076impl TokensRequestResponse {
1077 pub fn new(tokens: Vec<ResponseToken>, pagination_request: &PaginationResponse) -> Self {
1078 Self { tokens, pagination: pagination_request.clone() }
1079 }
1080}
1081
1082#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, Eq, Hash)]
1084#[serde(deny_unknown_fields)]
1085pub struct PaginationParams {
1086 #[serde(default)]
1088 pub page: i64,
1089 #[serde(default)]
1091 #[schema(default = 10)]
1092 pub page_size: i64,
1093}
1094
1095impl PaginationParams {
1096 pub fn new(page: i64, page_size: i64) -> Self {
1097 Self { page, page_size }
1098 }
1099}
1100
1101impl Default for PaginationParams {
1102 fn default() -> Self {
1103 PaginationParams { page: 0, page_size: 20 }
1104 }
1105}
1106
1107#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, Eq, Hash)]
1108#[serde(deny_unknown_fields)]
1109pub struct PaginationResponse {
1110 pub page: i64,
1111 pub page_size: i64,
1112 pub total: i64,
1114}
1115
1116impl PaginationResponse {
1118 pub fn new(page: i64, page_size: i64, total: i64) -> Self {
1119 Self { page, page_size, total }
1120 }
1121
1122 pub fn total_pages(&self) -> i64 {
1123 (self.total + self.page_size - 1) / self.page_size
1125 }
1126}
1127
1128#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, Default, ToSchema, Eq, Hash)]
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)]
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)]
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)]
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)]
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)]
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(Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Hash, Clone)]
1639pub struct TracedEntryPointRequestBody {
1640 #[serde(default)]
1641 pub chain: Chain,
1642 pub protocol_system: String,
1645 pub component_ids: Option<Vec<ComponentId>>,
1647 #[serde(default)]
1649 pub pagination: PaginationParams,
1650}
1651
1652#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema, Eq, Hash)]
1653pub struct EntryPoint {
1654 #[schema(example = "0xEdf63cce4bA70cbE74064b7687882E71ebB0e988:getRate()")]
1655 pub external_id: String,
1657 #[schema(value_type=String, example="0x8f4E8439b970363648421C692dd897Fb9c0Bd1D9")]
1658 #[serde(with = "hex_bytes")]
1659 pub target: Bytes,
1661 #[schema(example = "getRate()")]
1662 pub signature: String,
1664}
1665
1666#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema, Eq, Hash)]
1667pub enum StorageOverride {
1668 Diff(BTreeMap<StoreKey, StoreVal>),
1672
1673 Replace(BTreeMap<StoreKey, StoreVal>),
1677}
1678
1679impl From<models::blockchain::StorageOverride> for StorageOverride {
1680 fn from(value: models::blockchain::StorageOverride) -> Self {
1681 match value {
1682 models::blockchain::StorageOverride::Diff(diff) => StorageOverride::Diff(diff),
1683 models::blockchain::StorageOverride::Replace(replace) => {
1684 StorageOverride::Replace(replace)
1685 }
1686 }
1687 }
1688}
1689
1690#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema, Eq, Hash)]
1695pub struct AccountOverrides {
1696 pub slots: Option<StorageOverride>,
1698 pub native_balance: Option<Balance>,
1700 pub code: Option<Code>,
1702}
1703
1704impl From<models::blockchain::AccountOverrides> for AccountOverrides {
1705 fn from(value: models::blockchain::AccountOverrides) -> Self {
1706 AccountOverrides {
1707 slots: value.slots.map(|s| s.into()),
1708 native_balance: value.native_balance,
1709 code: value.code,
1710 }
1711 }
1712}
1713
1714#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema, Eq, Hash)]
1715pub struct RPCTracerParams {
1716 #[schema(value_type=Option<String>)]
1719 #[serde(with = "hex_bytes_option", default)]
1720 pub caller: Option<Bytes>,
1721 #[schema(value_type=String, example="0x679aefce")]
1723 #[serde(with = "hex_bytes")]
1724 pub calldata: Bytes,
1725 pub state_overrides: Option<BTreeMap<Address, AccountOverrides>>,
1727 #[schema(value_type=Option<Vec<String>>)]
1730 #[serde(default)]
1731 pub prune_addresses: Option<Vec<Address>>,
1732}
1733
1734impl From<models::blockchain::RPCTracerParams> for RPCTracerParams {
1735 fn from(value: models::blockchain::RPCTracerParams) -> Self {
1736 RPCTracerParams {
1737 caller: value.caller,
1738 calldata: value.calldata,
1739 state_overrides: value.state_overrides.map(|overrides| {
1740 overrides
1741 .into_iter()
1742 .map(|(address, account_overrides)| (address, account_overrides.into()))
1743 .collect()
1744 }),
1745 prune_addresses: value.prune_addresses,
1746 }
1747 }
1748}
1749
1750#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Hash)]
1751#[serde(tag = "method", rename_all = "lowercase")]
1752pub enum TracingParams {
1753 RPCTracer(RPCTracerParams),
1755}
1756
1757impl From<models::blockchain::TracingParams> for TracingParams {
1758 fn from(value: models::blockchain::TracingParams) -> Self {
1759 match value {
1760 models::blockchain::TracingParams::RPCTracer(params) => {
1761 TracingParams::RPCTracer(params.into())
1762 }
1763 }
1764 }
1765}
1766
1767impl From<models::blockchain::EntryPoint> for EntryPoint {
1768 fn from(value: models::blockchain::EntryPoint) -> Self {
1769 Self { external_id: value.external_id, target: value.target, signature: value.signature }
1770 }
1771}
1772
1773#[derive(Serialize, Deserialize, Debug, PartialEq, ToSchema, Eq, Clone)]
1774pub struct EntryPointWithTracingParams {
1775 pub entry_point: EntryPoint,
1777 pub params: TracingParams,
1779}
1780
1781impl From<models::blockchain::EntryPointWithTracingParams> for EntryPointWithTracingParams {
1782 fn from(value: models::blockchain::EntryPointWithTracingParams) -> Self {
1783 Self { entry_point: value.entry_point.into(), params: value.params.into() }
1784 }
1785}
1786
1787#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
1788pub struct AddressStorageLocation {
1789 pub key: StoreKey,
1790 pub offset: u8,
1791}
1792
1793impl AddressStorageLocation {
1794 pub fn new(key: StoreKey, offset: u8) -> Self {
1795 Self { key, offset }
1796 }
1797}
1798
1799impl From<models::blockchain::AddressStorageLocation> for AddressStorageLocation {
1800 fn from(value: models::blockchain::AddressStorageLocation) -> Self {
1801 Self { key: value.key, offset: value.offset }
1802 }
1803}
1804
1805fn deserialize_retriggers_from_value(
1806 value: &serde_json::Value,
1807) -> Result<HashSet<(StoreKey, AddressStorageLocation)>, String> {
1808 use serde::Deserialize;
1809 use serde_json::Value;
1810
1811 let mut result = HashSet::new();
1812
1813 if let Value::Array(items) = value {
1814 for item in items {
1815 if let Value::Array(pair) = item {
1816 if pair.len() == 2 {
1817 let key = StoreKey::deserialize(&pair[0])
1818 .map_err(|e| format!("Failed to deserialize key: {}", e))?;
1819
1820 let addr_storage = match &pair[1] {
1822 Value::String(_) => {
1823 let storage_key = StoreKey::deserialize(&pair[1]).map_err(|e| {
1825 format!("Failed to deserialize old format storage key: {}", e)
1826 })?;
1827 AddressStorageLocation::new(storage_key, 12)
1828 }
1829 Value::Object(_) => {
1830 AddressStorageLocation::deserialize(&pair[1]).map_err(|e| {
1832 format!("Failed to deserialize AddressStorageLocation: {}", e)
1833 })?
1834 }
1835 _ => return Err("Invalid retrigger format".to_string()),
1836 };
1837
1838 result.insert((key, addr_storage));
1839 }
1840 }
1841 }
1842 }
1843
1844 Ok(result)
1845}
1846
1847#[derive(Serialize, Debug, Default, PartialEq, ToSchema, Eq, Clone)]
1848pub struct TracingResult {
1849 #[schema(value_type=HashSet<(String, String)>)]
1850 pub retriggers: HashSet<(StoreKey, AddressStorageLocation)>,
1851 #[schema(value_type=HashMap<String,HashSet<String>>)]
1852 pub accessed_slots: HashMap<Address, HashSet<StoreKey>>,
1853}
1854
1855impl<'de> Deserialize<'de> for TracingResult {
1858 fn deserialize<D>(deserializer: D) -> Result<TracingResult, D::Error>
1859 where
1860 D: Deserializer<'de>,
1861 {
1862 use serde::de::Error;
1863 use serde_json::Value;
1864
1865 let value = Value::deserialize(deserializer)?;
1866 let mut result = TracingResult::default();
1867
1868 if let Value::Object(map) = value {
1869 if let Some(retriggers_value) = map.get("retriggers") {
1871 result.retriggers =
1872 deserialize_retriggers_from_value(retriggers_value).map_err(|e| {
1873 D::Error::custom(format!("Failed to deserialize retriggers: {}", e))
1874 })?;
1875 }
1876
1877 if let Some(accessed_slots_value) = map.get("accessed_slots") {
1879 result.accessed_slots = serde_json::from_value(accessed_slots_value.clone())
1880 .map_err(|e| {
1881 D::Error::custom(format!("Failed to deserialize accessed_slots: {}", e))
1882 })?;
1883 }
1884 }
1885
1886 Ok(result)
1887 }
1888}
1889
1890impl From<models::blockchain::TracingResult> for TracingResult {
1891 fn from(value: models::blockchain::TracingResult) -> Self {
1892 TracingResult {
1893 retriggers: value
1894 .retriggers
1895 .into_iter()
1896 .map(|(k, v)| (k, v.into()))
1897 .collect(),
1898 accessed_slots: value.accessed_slots,
1899 }
1900 }
1901}
1902
1903#[derive(Serialize, PartialEq, ToSchema, Eq, Clone, Debug, Deserialize)]
1904pub struct TracedEntryPointRequestResponse {
1905 pub traced_entry_points:
1908 HashMap<ComponentId, Vec<(EntryPointWithTracingParams, TracingResult)>>,
1909 pub pagination: PaginationResponse,
1910}
1911
1912impl From<TracedEntryPointRequestResponse> for DCIUpdate {
1913 fn from(response: TracedEntryPointRequestResponse) -> Self {
1914 let mut new_entrypoints = HashMap::new();
1915 let mut new_entrypoint_params = HashMap::new();
1916 let mut trace_results = HashMap::new();
1917
1918 for (component, traces) in response.traced_entry_points {
1919 let mut entrypoints = HashSet::new();
1920
1921 for (entrypoint, trace) in traces {
1922 let entrypoint_id = entrypoint
1923 .entry_point
1924 .external_id
1925 .clone();
1926
1927 entrypoints.insert(entrypoint.entry_point.clone());
1929
1930 new_entrypoint_params
1932 .entry(entrypoint_id.clone())
1933 .or_insert_with(HashSet::new)
1934 .insert((entrypoint.params, Some(component.clone())));
1935
1936 trace_results
1938 .entry(entrypoint_id)
1939 .and_modify(|existing_trace: &mut TracingResult| {
1940 existing_trace
1942 .retriggers
1943 .extend(trace.retriggers.clone());
1944 for (address, slots) in trace.accessed_slots.clone() {
1945 existing_trace
1946 .accessed_slots
1947 .entry(address)
1948 .or_default()
1949 .extend(slots);
1950 }
1951 })
1952 .or_insert(trace);
1953 }
1954
1955 if !entrypoints.is_empty() {
1956 new_entrypoints.insert(component, entrypoints);
1957 }
1958 }
1959
1960 DCIUpdate { new_entrypoints, new_entrypoint_params, trace_results }
1961 }
1962}
1963
1964#[derive(Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Clone)]
1965pub struct AddEntryPointRequestBody {
1966 #[serde(default)]
1967 pub chain: Chain,
1968 #[schema(value_type=String)]
1969 #[serde(default)]
1970 pub block_hash: Bytes,
1971 pub entry_points_with_tracing_data: Vec<(ComponentId, Vec<EntryPointWithTracingParams>)>,
1973}
1974
1975#[derive(Serialize, PartialEq, ToSchema, Eq, Clone, Debug, Deserialize)]
1976pub struct AddEntryPointRequestResponse {
1977 pub traced_entry_points:
1980 HashMap<ComponentId, Vec<(EntryPointWithTracingParams, TracingResult)>>,
1981}
1982
1983#[cfg(test)]
1984mod test {
1985 use std::str::FromStr;
1986
1987 use maplit::hashmap;
1988 use rstest::rstest;
1989
1990 use super::*;
1991
1992 #[test]
1993 fn test_tracing_result_backward_compatibility() {
1994 use serde_json::json;
1995
1996 let old_format_json = json!({
1998 "retriggers": [
1999 ["0x01", "0x02"],
2000 ["0x03", "0x04"]
2001 ],
2002 "accessed_slots": {
2003 "0x05": ["0x06", "0x07"]
2004 }
2005 });
2006
2007 let result: TracingResult = serde_json::from_value(old_format_json).unwrap();
2008
2009 assert_eq!(result.retriggers.len(), 2);
2011 let retriggers_vec: Vec<_> = result.retriggers.iter().collect();
2012 assert!(retriggers_vec.iter().any(|(k, v)| {
2013 k == &Bytes::from("0x01") && v.key == Bytes::from("0x02") && v.offset == 12
2014 }));
2015 assert!(retriggers_vec.iter().any(|(k, v)| {
2016 k == &Bytes::from("0x03") && v.key == Bytes::from("0x04") && v.offset == 12
2017 }));
2018
2019 let new_format_json = json!({
2021 "retriggers": [
2022 ["0x01", {"key": "0x02", "offset": 12}],
2023 ["0x03", {"key": "0x04", "offset": 5}]
2024 ],
2025 "accessed_slots": {
2026 "0x05": ["0x06", "0x07"]
2027 }
2028 });
2029
2030 let result2: TracingResult = serde_json::from_value(new_format_json).unwrap();
2031
2032 assert_eq!(result2.retriggers.len(), 2);
2034 let retriggers_vec2: Vec<_> = result2.retriggers.iter().collect();
2035 assert!(retriggers_vec2.iter().any(|(k, v)| {
2036 k == &Bytes::from("0x01") && v.key == Bytes::from("0x02") && v.offset == 12
2037 }));
2038 assert!(retriggers_vec2.iter().any(|(k, v)| {
2039 k == &Bytes::from("0x03") && v.key == Bytes::from("0x04") && v.offset == 5
2040 }));
2041 }
2042
2043 #[test]
2044 fn test_protocol_components_equality() {
2045 let body1 = ProtocolComponentsRequestBody {
2046 protocol_system: "protocol1".to_string(),
2047 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
2048 tvl_gt: Some(1000.0),
2049 chain: Chain::Ethereum,
2050 pagination: PaginationParams::default(),
2051 };
2052
2053 let body2 = ProtocolComponentsRequestBody {
2054 protocol_system: "protocol1".to_string(),
2055 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
2056 tvl_gt: Some(1000.0 + 1e-7), chain: Chain::Ethereum,
2058 pagination: PaginationParams::default(),
2059 };
2060
2061 assert_eq!(body1, body2);
2063 }
2064
2065 #[test]
2066 fn test_protocol_components_inequality() {
2067 let body1 = ProtocolComponentsRequestBody {
2068 protocol_system: "protocol1".to_string(),
2069 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
2070 tvl_gt: Some(1000.0),
2071 chain: Chain::Ethereum,
2072 pagination: PaginationParams::default(),
2073 };
2074
2075 let body2 = ProtocolComponentsRequestBody {
2076 protocol_system: "protocol1".to_string(),
2077 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
2078 tvl_gt: Some(1000.0 + 1e-5), chain: Chain::Ethereum,
2080 pagination: PaginationParams::default(),
2081 };
2082
2083 assert_ne!(body1, body2);
2085 }
2086
2087 #[test]
2088 fn test_parse_state_request() {
2089 let json_str = r#"
2090 {
2091 "contractIds": [
2092 "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092"
2093 ],
2094 "protocol_system": "uniswap_v2",
2095 "version": {
2096 "timestamp": "2069-01-01T04:20:00",
2097 "block": {
2098 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2099 "number": 213,
2100 "chain": "ethereum"
2101 }
2102 }
2103 }
2104 "#;
2105
2106 let result: StateRequestBody = serde_json::from_str(json_str).unwrap();
2107
2108 let contract0 = "b4eccE46b8D4e4abFd03C9B806276A6735C9c092"
2109 .parse()
2110 .unwrap();
2111 let block_hash = "24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4"
2112 .parse()
2113 .unwrap();
2114 let block_number = 213;
2115
2116 let expected_timestamp =
2117 NaiveDateTime::parse_from_str("2069-01-01T04:20:00", "%Y-%m-%dT%H:%M:%S").unwrap();
2118
2119 let expected = StateRequestBody {
2120 contract_ids: Some(vec![contract0]),
2121 protocol_system: "uniswap_v2".to_string(),
2122 version: VersionParam {
2123 timestamp: Some(expected_timestamp),
2124 block: Some(BlockParam {
2125 hash: Some(block_hash),
2126 chain: Some(Chain::Ethereum),
2127 number: Some(block_number),
2128 }),
2129 },
2130 chain: Chain::Ethereum,
2131 pagination: PaginationParams::default(),
2132 };
2133
2134 assert_eq!(result, expected);
2135 }
2136
2137 #[test]
2138 fn test_parse_state_request_dual_interface() {
2139 let json_common = r#"
2140 {
2141 "__CONTRACT_IDS__": [
2142 "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092"
2143 ],
2144 "version": {
2145 "timestamp": "2069-01-01T04:20:00",
2146 "block": {
2147 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2148 "number": 213,
2149 "chain": "ethereum"
2150 }
2151 }
2152 }
2153 "#;
2154
2155 let json_str_snake = json_common.replace("\"__CONTRACT_IDS__\"", "\"contract_ids\"");
2156 let json_str_camel = json_common.replace("\"__CONTRACT_IDS__\"", "\"contractIds\"");
2157
2158 let snake: StateRequestBody = serde_json::from_str(&json_str_snake).unwrap();
2159 let camel: StateRequestBody = serde_json::from_str(&json_str_camel).unwrap();
2160
2161 assert_eq!(snake, camel);
2162 }
2163
2164 #[test]
2165 fn test_parse_state_request_unknown_field() {
2166 let body = r#"
2167 {
2168 "contract_ids_with_typo_error": [
2169 {
2170 "address": "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092",
2171 "chain": "ethereum"
2172 }
2173 ],
2174 "version": {
2175 "timestamp": "2069-01-01T04:20:00",
2176 "block": {
2177 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2178 "parentHash": "0x8d75152454e60413efe758cc424bfd339897062d7e658f302765eb7b50971815",
2179 "number": 213,
2180 "chain": "ethereum"
2181 }
2182 }
2183 }
2184 "#;
2185
2186 let decoded = serde_json::from_str::<StateRequestBody>(body);
2187
2188 assert!(decoded.is_err(), "Expected an error due to unknown field");
2189
2190 if let Err(e) = decoded {
2191 assert!(
2192 e.to_string()
2193 .contains("unknown field `contract_ids_with_typo_error`"),
2194 "Error message does not contain expected unknown field information"
2195 );
2196 }
2197 }
2198
2199 #[test]
2200 fn test_parse_state_request_no_contract_specified() {
2201 let json_str = r#"
2202 {
2203 "protocol_system": "uniswap_v2",
2204 "version": {
2205 "timestamp": "2069-01-01T04:20:00",
2206 "block": {
2207 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2208 "number": 213,
2209 "chain": "ethereum"
2210 }
2211 }
2212 }
2213 "#;
2214
2215 let result: StateRequestBody = serde_json::from_str(json_str).unwrap();
2216
2217 let block_hash = "24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4".into();
2218 let block_number = 213;
2219 let expected_timestamp =
2220 NaiveDateTime::parse_from_str("2069-01-01T04:20:00", "%Y-%m-%dT%H:%M:%S").unwrap();
2221
2222 let expected = StateRequestBody {
2223 contract_ids: None,
2224 protocol_system: "uniswap_v2".to_string(),
2225 version: VersionParam {
2226 timestamp: Some(expected_timestamp),
2227 block: Some(BlockParam {
2228 hash: Some(block_hash),
2229 chain: Some(Chain::Ethereum),
2230 number: Some(block_number),
2231 }),
2232 },
2233 chain: Chain::Ethereum,
2234 pagination: PaginationParams { page: 0, page_size: 20 },
2235 };
2236
2237 assert_eq!(result, expected);
2238 }
2239
2240 #[rstest]
2241 #[case::deprecated_ids(
2242 r#"
2243 {
2244 "protocol_ids": [
2245 {
2246 "id": "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092",
2247 "chain": "ethereum"
2248 }
2249 ],
2250 "protocol_system": "uniswap_v2",
2251 "include_balances": false,
2252 "version": {
2253 "timestamp": "2069-01-01T04:20:00",
2254 "block": {
2255 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2256 "number": 213,
2257 "chain": "ethereum"
2258 }
2259 }
2260 }
2261 "#
2262 )]
2263 #[case(
2264 r#"
2265 {
2266 "protocolIds": [
2267 "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092"
2268 ],
2269 "protocol_system": "uniswap_v2",
2270 "include_balances": false,
2271 "version": {
2272 "timestamp": "2069-01-01T04:20:00",
2273 "block": {
2274 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
2275 "number": 213,
2276 "chain": "ethereum"
2277 }
2278 }
2279 }
2280 "#
2281 )]
2282 fn test_parse_protocol_state_request(#[case] json_str: &str) {
2283 let result: ProtocolStateRequestBody = serde_json::from_str(json_str).unwrap();
2284
2285 let block_hash = "24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4"
2286 .parse()
2287 .unwrap();
2288 let block_number = 213;
2289
2290 let expected_timestamp =
2291 NaiveDateTime::parse_from_str("2069-01-01T04:20:00", "%Y-%m-%dT%H:%M:%S").unwrap();
2292
2293 let expected = ProtocolStateRequestBody {
2294 protocol_ids: Some(vec!["0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092".to_string()]),
2295 protocol_system: "uniswap_v2".to_string(),
2296 version: VersionParam {
2297 timestamp: Some(expected_timestamp),
2298 block: Some(BlockParam {
2299 hash: Some(block_hash),
2300 chain: Some(Chain::Ethereum),
2301 number: Some(block_number),
2302 }),
2303 },
2304 chain: Chain::Ethereum,
2305 include_balances: false,
2306 pagination: PaginationParams::default(),
2307 };
2308
2309 assert_eq!(result, expected);
2310 }
2311
2312 #[rstest]
2313 #[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()])]
2314 #[case::with_strings(vec!["id1".to_string(), "id2".to_string()], vec!["id1".to_string(), "id2".to_string()])]
2315 fn test_id_filtered<T>(#[case] input_ids: Vec<T>, #[case] expected_ids: Vec<String>)
2316 where
2317 T: Into<String> + Clone,
2318 {
2319 let request_body = ProtocolStateRequestBody::id_filtered(input_ids);
2320
2321 assert_eq!(request_body.protocol_ids, Some(expected_ids));
2322 }
2323
2324 fn create_models_block_changes() -> crate::models::blockchain::BlockAggregatedChanges {
2325 let base_ts = 1694534400; crate::models::blockchain::BlockAggregatedChanges {
2328 extractor: "native_name".to_string(),
2329 block: models::blockchain::Block::new(
2330 3,
2331 models::Chain::Ethereum,
2332 Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000003").unwrap(),
2333 Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000002").unwrap(),
2334 chrono::DateTime::from_timestamp(base_ts + 3000, 0).unwrap().naive_utc(),
2335 ),
2336 db_committed_upto_block_height: 1,
2337 finalized_block_height: 1,
2338 revert: true,
2339 state_deltas: HashMap::from([
2340 ("pc_1".to_string(), models::protocol::ProtocolComponentStateDelta {
2341 component_id: "pc_1".to_string(),
2342 updated_attributes: HashMap::from([
2343 ("attr_2".to_string(), Bytes::from("0x0000000000000002")),
2344 ("attr_1".to_string(), Bytes::from("0x00000000000003e8")),
2345 ]),
2346 deleted_attributes: HashSet::new(),
2347 }),
2348 ]),
2349 new_protocol_components: HashMap::from([
2350 ("pc_2".to_string(), crate::models::protocol::ProtocolComponent {
2351 id: "pc_2".to_string(),
2352 protocol_system: "native_protocol_system".to_string(),
2353 protocol_type_name: "pt_1".to_string(),
2354 chain: models::Chain::Ethereum,
2355 tokens: vec![
2356 Bytes::from_str("0xdac17f958d2ee523a2206206994597c13d831ec7").unwrap(),
2357 Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
2358 ],
2359 contract_addresses: vec![],
2360 static_attributes: HashMap::new(),
2361 change: models::ChangeType::Creation,
2362 creation_tx: Bytes::from_str("0x000000000000000000000000000000000000000000000000000000000000c351").unwrap(),
2363 created_at: chrono::DateTime::from_timestamp(base_ts + 5000, 0).unwrap().naive_utc(),
2364 }),
2365 ]),
2366 deleted_protocol_components: HashMap::from([
2367 ("pc_3".to_string(), crate::models::protocol::ProtocolComponent {
2368 id: "pc_3".to_string(),
2369 protocol_system: "native_protocol_system".to_string(),
2370 protocol_type_name: "pt_2".to_string(),
2371 chain: models::Chain::Ethereum,
2372 tokens: vec![
2373 Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(),
2374 Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(),
2375 ],
2376 contract_addresses: vec![],
2377 static_attributes: HashMap::new(),
2378 change: models::ChangeType::Deletion,
2379 creation_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000009c41").unwrap(),
2380 created_at: chrono::DateTime::from_timestamp(base_ts + 4000, 0).unwrap().naive_utc(),
2381 }),
2382 ]),
2383 component_balances: HashMap::from([
2384 ("pc_1".to_string(), HashMap::from([
2385 (Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(), models::protocol::ComponentBalance {
2386 token: Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
2387 balance: Bytes::from("0x00000001"),
2388 balance_float: 1.0,
2389 modify_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
2390 component_id: "pc_1".to_string(),
2391 }),
2392 (Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(), models::protocol::ComponentBalance {
2393 token: Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(),
2394 balance: Bytes::from("0x000003e8"),
2395 balance_float: 1000.0,
2396 modify_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000007531").unwrap(),
2397 component_id: "pc_1".to_string(),
2398 }),
2399 ])),
2400 ]),
2401 account_balances: HashMap::from([
2402 (Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(), HashMap::from([
2403 (Bytes::from_str("0x7a250d5630b4cf539739df2c5dacb4c659f2488d").unwrap(), models::contract::AccountBalance {
2404 account: Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(),
2405 token: Bytes::from_str("0x7a250d5630b4cf539739df2c5dacb4c659f2488d").unwrap(),
2406 balance: Bytes::from("0x000003e8"),
2407 modify_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000007531").unwrap(),
2408 }),
2409 ])),
2410 ]),
2411 ..Default::default()
2412 }
2413 }
2414
2415 #[test]
2416 fn test_serialize_deserialize_block_changes() {
2417 let block_entity_changes = create_models_block_changes();
2422
2423 let json_data = serde_json::to_string(&block_entity_changes).expect("Failed to serialize");
2425
2426 serde_json::from_str::<BlockChanges>(&json_data).expect("parsing failed");
2428 }
2429
2430 #[test]
2431 fn test_parse_block_changes() {
2432 let json_data = r#"
2433 {
2434 "extractor": "vm:ambient",
2435 "chain": "ethereum",
2436 "block": {
2437 "number": 123,
2438 "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2439 "parent_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
2440 "chain": "ethereum",
2441 "ts": "2023-09-14T00:00:00"
2442 },
2443 "finalized_block_height": 0,
2444 "revert": false,
2445 "new_tokens": {},
2446 "account_updates": {
2447 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2448 "address": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2449 "chain": "ethereum",
2450 "slots": {},
2451 "balance": "0x01f4",
2452 "code": "",
2453 "change": "Update"
2454 }
2455 },
2456 "state_updates": {
2457 "component_1": {
2458 "component_id": "component_1",
2459 "updated_attributes": {"attr1": "0x01"},
2460 "deleted_attributes": ["attr2"]
2461 }
2462 },
2463 "new_protocol_components":
2464 { "protocol_1": {
2465 "id": "protocol_1",
2466 "protocol_system": "system_1",
2467 "protocol_type_name": "type_1",
2468 "chain": "ethereum",
2469 "tokens": ["0x01", "0x02"],
2470 "contract_ids": ["0x01", "0x02"],
2471 "static_attributes": {"attr1": "0x01f4"},
2472 "change": "Update",
2473 "creation_tx": "0x01",
2474 "created_at": "2023-09-14T00:00:00"
2475 }
2476 },
2477 "deleted_protocol_components": {},
2478 "component_balances": {
2479 "protocol_1":
2480 {
2481 "0x01": {
2482 "token": "0x01",
2483 "balance": "0xb77831d23691653a01",
2484 "balance_float": 3.3844151001790677e21,
2485 "modify_tx": "0x01",
2486 "component_id": "protocol_1"
2487 }
2488 }
2489 },
2490 "account_balances": {
2491 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2492 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2493 "account": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2494 "token": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2495 "balance": "0x01f4",
2496 "modify_tx": "0x01"
2497 }
2498 }
2499 },
2500 "component_tvl": {
2501 "protocol_1": 1000.0
2502 },
2503 "dci_update": {
2504 "new_entrypoints": {
2505 "component_1": [
2506 {
2507 "external_id": "0x01:sig()",
2508 "target": "0x01",
2509 "signature": "sig()"
2510 }
2511 ]
2512 },
2513 "new_entrypoint_params": {
2514 "0x01:sig()": [
2515 [
2516 {
2517 "method": "rpctracer",
2518 "caller": "0x01",
2519 "calldata": "0x02"
2520 },
2521 "component_1"
2522 ]
2523 ]
2524 },
2525 "trace_results": {
2526 "0x01:sig()": {
2527 "retriggers": [
2528 ["0x01", {"key": "0x02", "offset": 12}]
2529 ],
2530 "accessed_slots": {
2531 "0x03": ["0x03", "0x04"]
2532 }
2533 }
2534 }
2535 }
2536 }
2537 "#;
2538
2539 serde_json::from_str::<BlockChanges>(json_data).expect("parsing failed");
2540 }
2541
2542 #[test]
2543 fn test_parse_websocket_message() {
2544 let json_data = r#"
2545 {
2546 "subscription_id": "5d23bfbe-89ad-4ea3-8672-dc9e973ac9dc",
2547 "deltas": {
2548 "type": "BlockChanges",
2549 "extractor": "uniswap_v2",
2550 "chain": "ethereum",
2551 "block": {
2552 "number": 19291517,
2553 "hash": "0xbc3ea4896c0be8da6229387a8571b72818aa258daf4fab46471003ad74c4ee83",
2554 "parent_hash": "0x89ca5b8d593574cf6c886f41ef8208bf6bdc1a90ef36046cb8c84bc880b9af8f",
2555 "chain": "ethereum",
2556 "ts": "2024-02-23T16:35:35"
2557 },
2558 "finalized_block_height": 0,
2559 "revert": false,
2560 "new_tokens": {},
2561 "account_updates": {
2562 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2563 "address": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2564 "chain": "ethereum",
2565 "slots": {},
2566 "balance": "0x01f4",
2567 "code": "",
2568 "change": "Update"
2569 }
2570 },
2571 "state_updates": {
2572 "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28": {
2573 "component_id": "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28",
2574 "updated_attributes": {
2575 "reserve0": "0x87f7b5973a7f28a8b32404",
2576 "reserve1": "0x09e9564b11"
2577 },
2578 "deleted_attributes": []
2579 },
2580 "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d": {
2581 "component_id": "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d",
2582 "updated_attributes": {
2583 "reserve1": "0x44d9a8fd662c2f4d03",
2584 "reserve0": "0x500b1261f811d5bf423e"
2585 },
2586 "deleted_attributes": []
2587 }
2588 },
2589 "new_protocol_components": {},
2590 "deleted_protocol_components": {},
2591 "component_balances": {
2592 "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d": {
2593 "0x9012744b7a564623b6c3e40b144fc196bdedf1a9": {
2594 "token": "0x9012744b7a564623b6c3e40b144fc196bdedf1a9",
2595 "balance": "0x500b1261f811d5bf423e",
2596 "balance_float": 3.779935574269033E23,
2597 "modify_tx": "0xe46c4db085fb6c6f3408a65524555797adb264e1d5cf3b66ad154598f85ac4bf",
2598 "component_id": "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d"
2599 },
2600 "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": {
2601 "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
2602 "balance": "0x44d9a8fd662c2f4d03",
2603 "balance_float": 1.270062661329837E21,
2604 "modify_tx": "0xe46c4db085fb6c6f3408a65524555797adb264e1d5cf3b66ad154598f85ac4bf",
2605 "component_id": "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d"
2606 }
2607 }
2608 },
2609 "account_balances": {
2610 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2611 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2612 "account": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2613 "token": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2614 "balance": "0x01f4",
2615 "modify_tx": "0x01"
2616 }
2617 }
2618 },
2619 "component_tvl": {},
2620 "dci_update": {
2621 "new_entrypoints": {
2622 "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28": [
2623 {
2624 "external_id": "0x01:sig()",
2625 "target": "0x01",
2626 "signature": "sig()"
2627 }
2628 ]
2629 },
2630 "new_entrypoint_params": {
2631 "0x01:sig()": [
2632 [
2633 {
2634 "method": "rpctracer",
2635 "caller": "0x01",
2636 "calldata": "0x02"
2637 },
2638 "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28"
2639 ]
2640 ]
2641 },
2642 "trace_results": {
2643 "0x01:sig()": {
2644 "retriggers": [
2645 ["0x01", {"key": "0x02", "offset": 12}]
2646 ],
2647 "accessed_slots": {
2648 "0x03": ["0x03", "0x04"]
2649 }
2650 }
2651 }
2652 }
2653 }
2654 }
2655 "#;
2656 serde_json::from_str::<WebSocketMessage>(json_data).expect("parsing failed");
2657 }
2658
2659 #[test]
2660 fn test_protocol_state_delta_merge_update_delete() {
2661 let mut delta1 = ProtocolStateDelta {
2663 component_id: "Component1".to_string(),
2664 updated_attributes: HashMap::from([(
2665 "Attribute1".to_string(),
2666 Bytes::from("0xbadbabe420"),
2667 )]),
2668 deleted_attributes: HashSet::new(),
2669 };
2670 let delta2 = ProtocolStateDelta {
2671 component_id: "Component1".to_string(),
2672 updated_attributes: HashMap::from([(
2673 "Attribute2".to_string(),
2674 Bytes::from("0x0badbabe"),
2675 )]),
2676 deleted_attributes: HashSet::from(["Attribute1".to_string()]),
2677 };
2678 let exp = ProtocolStateDelta {
2679 component_id: "Component1".to_string(),
2680 updated_attributes: HashMap::from([(
2681 "Attribute2".to_string(),
2682 Bytes::from("0x0badbabe"),
2683 )]),
2684 deleted_attributes: HashSet::from(["Attribute1".to_string()]),
2685 };
2686
2687 delta1.merge(&delta2);
2688
2689 assert_eq!(delta1, exp);
2690 }
2691
2692 #[test]
2693 fn test_protocol_state_delta_merge_delete_update() {
2694 let mut delta1 = ProtocolStateDelta {
2696 component_id: "Component1".to_string(),
2697 updated_attributes: HashMap::new(),
2698 deleted_attributes: HashSet::from(["Attribute1".to_string()]),
2699 };
2700 let delta2 = ProtocolStateDelta {
2701 component_id: "Component1".to_string(),
2702 updated_attributes: HashMap::from([(
2703 "Attribute1".to_string(),
2704 Bytes::from("0x0badbabe"),
2705 )]),
2706 deleted_attributes: HashSet::new(),
2707 };
2708 let exp = ProtocolStateDelta {
2709 component_id: "Component1".to_string(),
2710 updated_attributes: HashMap::from([(
2711 "Attribute1".to_string(),
2712 Bytes::from("0x0badbabe"),
2713 )]),
2714 deleted_attributes: HashSet::new(),
2715 };
2716
2717 delta1.merge(&delta2);
2718
2719 assert_eq!(delta1, exp);
2720 }
2721
2722 #[test]
2723 fn test_account_update_merge() {
2724 let mut account1 = AccountUpdate::new(
2726 Bytes::from(b"0x1234"),
2727 Chain::Ethereum,
2728 HashMap::from([(Bytes::from("0xaabb"), Bytes::from("0xccdd"))]),
2729 Some(Bytes::from("0x1000")),
2730 Some(Bytes::from("0xdeadbeaf")),
2731 ChangeType::Creation,
2732 );
2733
2734 let account2 = AccountUpdate::new(
2735 Bytes::from(b"0x1234"), Chain::Ethereum,
2737 HashMap::from([(Bytes::from("0xeeff"), Bytes::from("0x11223344"))]),
2738 Some(Bytes::from("0x2000")),
2739 Some(Bytes::from("0xcafebabe")),
2740 ChangeType::Update,
2741 );
2742
2743 account1.merge(&account2);
2745
2746 let expected = AccountUpdate::new(
2748 Bytes::from(b"0x1234"), Chain::Ethereum,
2750 HashMap::from([
2751 (Bytes::from("0xaabb"), Bytes::from("0xccdd")), (Bytes::from("0xeeff"), Bytes::from("0x11223344")), ]),
2754 Some(Bytes::from("0x2000")), Some(Bytes::from("0xcafebabe")), ChangeType::Creation, );
2758
2759 assert_eq!(account1, expected);
2761 }
2762
2763 #[test]
2764 fn test_block_account_changes_merge() {
2765 let old_account_updates: HashMap<Bytes, AccountUpdate> = [(
2767 Bytes::from("0x0011"),
2768 AccountUpdate {
2769 address: Bytes::from("0x00"),
2770 chain: Chain::Ethereum,
2771 slots: HashMap::from([(Bytes::from("0x0022"), Bytes::from("0x0033"))]),
2772 balance: Some(Bytes::from("0x01")),
2773 code: Some(Bytes::from("0x02")),
2774 change: ChangeType::Creation,
2775 },
2776 )]
2777 .into_iter()
2778 .collect();
2779 let new_account_updates: HashMap<Bytes, AccountUpdate> = [(
2780 Bytes::from("0x0011"),
2781 AccountUpdate {
2782 address: Bytes::from("0x00"),
2783 chain: Chain::Ethereum,
2784 slots: HashMap::from([(Bytes::from("0x0044"), Bytes::from("0x0055"))]),
2785 balance: Some(Bytes::from("0x03")),
2786 code: Some(Bytes::from("0x04")),
2787 change: ChangeType::Update,
2788 },
2789 )]
2790 .into_iter()
2791 .collect();
2792 let block_account_changes_initial = BlockChanges {
2794 extractor: "extractor1".to_string(),
2795 revert: false,
2796 account_updates: old_account_updates,
2797 ..Default::default()
2798 };
2799
2800 let block_account_changes_new = BlockChanges {
2801 extractor: "extractor2".to_string(),
2802 revert: true,
2803 account_updates: new_account_updates,
2804 ..Default::default()
2805 };
2806
2807 let res = block_account_changes_initial.merge(block_account_changes_new);
2809
2810 let expected_account_updates: HashMap<Bytes, AccountUpdate> = [(
2812 Bytes::from("0x0011"),
2813 AccountUpdate {
2814 address: Bytes::from("0x00"),
2815 chain: Chain::Ethereum,
2816 slots: HashMap::from([
2817 (Bytes::from("0x0044"), Bytes::from("0x0055")),
2818 (Bytes::from("0x0022"), Bytes::from("0x0033")),
2819 ]),
2820 balance: Some(Bytes::from("0x03")),
2821 code: Some(Bytes::from("0x04")),
2822 change: ChangeType::Creation,
2823 },
2824 )]
2825 .into_iter()
2826 .collect();
2827 let block_account_changes_expected = BlockChanges {
2828 extractor: "extractor1".to_string(),
2829 revert: true,
2830 account_updates: expected_account_updates,
2831 ..Default::default()
2832 };
2833 assert_eq!(res, block_account_changes_expected);
2834 }
2835
2836 #[test]
2837 fn test_block_entity_changes_merge() {
2838 let block_entity_changes_result1 = BlockChanges {
2840 extractor: String::from("extractor1"),
2841 revert: false,
2842 state_updates: hashmap! { "state1".to_string() => ProtocolStateDelta::default() },
2843 new_protocol_components: hashmap! { "component1".to_string() => ProtocolComponent::default() },
2844 deleted_protocol_components: HashMap::new(),
2845 component_balances: hashmap! {
2846 "component1".to_string() => TokenBalances(hashmap! {
2847 Bytes::from("0x01") => ComponentBalance {
2848 token: Bytes::from("0x01"),
2849 balance: Bytes::from("0x01"),
2850 balance_float: 1.0,
2851 modify_tx: Bytes::from("0x00"),
2852 component_id: "component1".to_string()
2853 },
2854 Bytes::from("0x02") => ComponentBalance {
2855 token: Bytes::from("0x02"),
2856 balance: Bytes::from("0x02"),
2857 balance_float: 2.0,
2858 modify_tx: Bytes::from("0x00"),
2859 component_id: "component1".to_string()
2860 },
2861 })
2862
2863 },
2864 component_tvl: hashmap! { "tvl1".to_string() => 1000.0 },
2865 ..Default::default()
2866 };
2867 let block_entity_changes_result2 = BlockChanges {
2868 extractor: String::from("extractor2"),
2869 revert: true,
2870 state_updates: hashmap! { "state2".to_string() => ProtocolStateDelta::default() },
2871 new_protocol_components: hashmap! { "component2".to_string() => ProtocolComponent::default() },
2872 deleted_protocol_components: hashmap! { "component3".to_string() => ProtocolComponent::default() },
2873 component_balances: hashmap! {
2874 "component1".to_string() => TokenBalances::default(),
2875 "component2".to_string() => TokenBalances::default()
2876 },
2877 component_tvl: hashmap! { "tvl2".to_string() => 2000.0 },
2878 ..Default::default()
2879 };
2880
2881 let res = block_entity_changes_result1.merge(block_entity_changes_result2);
2882
2883 let expected_block_entity_changes_result = BlockChanges {
2884 extractor: String::from("extractor1"),
2885 revert: true,
2886 state_updates: hashmap! {
2887 "state1".to_string() => ProtocolStateDelta::default(),
2888 "state2".to_string() => ProtocolStateDelta::default(),
2889 },
2890 new_protocol_components: hashmap! {
2891 "component1".to_string() => ProtocolComponent::default(),
2892 "component2".to_string() => ProtocolComponent::default(),
2893 },
2894 deleted_protocol_components: hashmap! {
2895 "component3".to_string() => ProtocolComponent::default(),
2896 },
2897 component_balances: hashmap! {
2898 "component1".to_string() => TokenBalances(hashmap! {
2899 Bytes::from("0x01") => ComponentBalance {
2900 token: Bytes::from("0x01"),
2901 balance: Bytes::from("0x01"),
2902 balance_float: 1.0,
2903 modify_tx: Bytes::from("0x00"),
2904 component_id: "component1".to_string()
2905 },
2906 Bytes::from("0x02") => ComponentBalance {
2907 token: Bytes::from("0x02"),
2908 balance: Bytes::from("0x02"),
2909 balance_float: 2.0,
2910 modify_tx: Bytes::from("0x00"),
2911 component_id: "component1".to_string()
2912 },
2913 }),
2914 "component2".to_string() => TokenBalances::default(),
2915 },
2916 component_tvl: hashmap! {
2917 "tvl1".to_string() => 1000.0,
2918 "tvl2".to_string() => 2000.0
2919 },
2920 ..Default::default()
2921 };
2922
2923 assert_eq!(res, expected_block_entity_changes_result);
2924 }
2925
2926 #[test]
2927 fn test_websocket_error_serialization() {
2928 let extractor_id = ExtractorIdentity::new(Chain::Ethereum, "test_extractor");
2929 let subscription_id = Uuid::new_v4();
2930
2931 let error = WebsocketError::ExtractorNotFound(extractor_id.clone());
2933 let json = serde_json::to_string(&error).unwrap();
2934 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
2935 assert_eq!(error, deserialized);
2936
2937 let error = WebsocketError::SubscriptionNotFound(subscription_id);
2939 let json = serde_json::to_string(&error).unwrap();
2940 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
2941 assert_eq!(error, deserialized);
2942
2943 let error = WebsocketError::ParseError("{asd".to_string(), "invalid json".to_string());
2945 let json = serde_json::to_string(&error).unwrap();
2946 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
2947 assert_eq!(error, deserialized);
2948
2949 let error = WebsocketError::SubscribeError(extractor_id.clone());
2951 let json = serde_json::to_string(&error).unwrap();
2952 let deserialized: WebsocketError = serde_json::from_str(&json).unwrap();
2953 assert_eq!(error, deserialized);
2954 }
2955
2956 #[test]
2957 fn test_websocket_message_with_error_response() {
2958 let error =
2959 WebsocketError::ParseError("}asdfas".to_string(), "malformed request".to_string());
2960 let response = Response::Error(error.clone());
2961 let message = WebSocketMessage::Response(response);
2962
2963 let json = serde_json::to_string(&message).unwrap();
2964 let deserialized: WebSocketMessage = serde_json::from_str(&json).unwrap();
2965
2966 if let WebSocketMessage::Response(Response::Error(deserialized_error)) = deserialized {
2967 assert_eq!(error, deserialized_error);
2968 } else {
2969 panic!("Expected WebSocketMessage::Response(Response::Error)");
2970 }
2971 }
2972
2973 #[test]
2974 fn test_websocket_error_conversion_from_models() {
2975 use crate::models::error::WebsocketError as ModelsError;
2976
2977 let extractor_id =
2978 crate::models::ExtractorIdentity::new(crate::models::Chain::Ethereum, "test");
2979 let subscription_id = Uuid::new_v4();
2980
2981 let models_error = ModelsError::ExtractorNotFound(extractor_id.clone());
2983 let dto_error: WebsocketError = models_error.into();
2984 assert_eq!(dto_error, WebsocketError::ExtractorNotFound(extractor_id.clone().into()));
2985
2986 let models_error = ModelsError::SubscriptionNotFound(subscription_id);
2988 let dto_error: WebsocketError = models_error.into();
2989 assert_eq!(dto_error, WebsocketError::SubscriptionNotFound(subscription_id));
2990
2991 let json_result: Result<serde_json::Value, _> = serde_json::from_str("{invalid json");
2993 let json_error = json_result.unwrap_err();
2994 let models_error = ModelsError::ParseError("{invalid json".to_string(), json_error);
2995 let dto_error: WebsocketError = models_error.into();
2996 if let WebsocketError::ParseError(msg, error) = dto_error {
2997 assert!(!error.is_empty(), "Error message should not be empty, got: '{}'", msg);
2999 } else {
3000 panic!("Expected ParseError variant");
3001 }
3002
3003 let models_error = ModelsError::SubscribeError(extractor_id.clone());
3005 let dto_error: WebsocketError = models_error.into();
3006 assert_eq!(dto_error, WebsocketError::SubscribeError(extractor_id.into()));
3007 }
3008}
3009
3010#[cfg(test)]
3011mod memory_size_tests {
3012 use std::collections::HashMap;
3013
3014 use super::*;
3015
3016 #[test]
3017 fn test_state_request_response_memory_size_empty() {
3018 let response = StateRequestResponse {
3019 accounts: vec![],
3020 pagination: PaginationResponse::new(1, 10, 0),
3021 };
3022
3023 let size = response.memory_size();
3024
3025 assert!(size >= 128, "Empty response should have minimum size of 128 bytes, got {}", size);
3027 assert!(size < 200, "Empty response should not be too large, got {}", size);
3028 }
3029
3030 #[test]
3031 fn test_state_request_response_memory_size_scales_with_slots() {
3032 let create_response_with_slots = |slot_count: usize| {
3033 let mut slots = HashMap::new();
3034 for i in 0..slot_count {
3035 let key = vec![i as u8; 32]; let value = vec![(i + 100) as u8; 32]; slots.insert(key.into(), value.into());
3038 }
3039
3040 let account = ResponseAccount::new(
3041 Chain::Ethereum,
3042 vec![1; 20].into(),
3043 "Pool".to_string(),
3044 slots,
3045 vec![1; 32].into(),
3046 HashMap::new(),
3047 vec![].into(), vec![1; 32].into(),
3049 vec![1; 32].into(),
3050 vec![1; 32].into(),
3051 None,
3052 );
3053
3054 StateRequestResponse {
3055 accounts: vec![account],
3056 pagination: PaginationResponse::new(1, 10, 1),
3057 }
3058 };
3059
3060 let small_response = create_response_with_slots(10);
3061 let large_response = create_response_with_slots(100);
3062
3063 let small_size = small_response.memory_size();
3064 let large_size = large_response.memory_size();
3065
3066 assert!(
3068 large_size > small_size * 5,
3069 "Large response ({} bytes) should be much larger than small response ({} bytes)",
3070 large_size,
3071 small_size
3072 );
3073
3074 let size_diff = large_size - small_size;
3076 let expected_min_diff = 90 * 64; assert!(
3078 size_diff > expected_min_diff,
3079 "Size difference ({} bytes) should reflect the additional slot data",
3080 size_diff
3081 );
3082 }
3083}