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