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