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