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