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, 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)]
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)]
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
370#[derive(PartialEq, Serialize, Deserialize, Clone, Debug, ToSchema)]
371pub struct AccountUpdate {
372 #[serde(with = "hex_bytes")]
373 #[schema(value_type=Vec<String>)]
374 pub address: Bytes,
375 pub chain: Chain,
376 #[serde(with = "hex_hashmap_key_value")]
377 #[schema(value_type=HashMap<String, String>)]
378 pub slots: HashMap<Bytes, Bytes>,
379 #[serde(with = "hex_bytes_option")]
380 #[schema(value_type=Option<String>)]
381 pub balance: Option<Bytes>,
382 #[serde(with = "hex_bytes_option")]
383 #[schema(value_type=Option<String>)]
384 pub code: Option<Bytes>,
385 pub change: ChangeType,
386}
387
388impl AccountUpdate {
389 pub fn new(
390 address: Bytes,
391 chain: Chain,
392 slots: HashMap<Bytes, Bytes>,
393 balance: Option<Bytes>,
394 code: Option<Bytes>,
395 change: ChangeType,
396 ) -> Self {
397 Self { address, chain, slots, balance, code, change }
398 }
399
400 pub fn merge(&mut self, other: &Self) {
401 self.slots.extend(
402 other
403 .slots
404 .iter()
405 .map(|(k, v)| (k.clone(), v.clone())),
406 );
407 self.balance.clone_from(&other.balance);
408 self.code.clone_from(&other.code);
409 self.change = self.change.merge(&other.change);
410 }
411}
412
413impl From<models::contract::AccountDelta> for AccountUpdate {
414 fn from(value: models::contract::AccountDelta) -> Self {
415 AccountUpdate::new(
416 value.address,
417 value.chain.into(),
418 value
419 .slots
420 .into_iter()
421 .map(|(k, v)| (k, v.unwrap_or_default()))
422 .collect(),
423 value.balance,
424 value.code,
425 value.change.into(),
426 )
427 }
428}
429
430#[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize, ToSchema)]
432pub struct ProtocolComponent {
433 pub id: String,
435 pub protocol_system: String,
437 pub protocol_type_name: String,
439 pub chain: Chain,
440 #[schema(value_type=Vec<String>)]
442 pub tokens: Vec<Bytes>,
443 #[serde(alias = "contract_addresses")]
446 #[schema(value_type=Vec<String>)]
447 pub contract_ids: Vec<Bytes>,
448 #[serde(with = "hex_hashmap_value")]
450 #[schema(value_type=HashMap<String, String>)]
451 pub static_attributes: HashMap<String, Bytes>,
452 #[serde(default)]
454 pub change: ChangeType,
455 #[serde(with = "hex_bytes")]
457 #[schema(value_type=String)]
458 pub creation_tx: Bytes,
459 pub created_at: NaiveDateTime,
461}
462
463impl From<models::protocol::ProtocolComponent> for ProtocolComponent {
464 fn from(value: models::protocol::ProtocolComponent) -> Self {
465 Self {
466 id: value.id,
467 protocol_system: value.protocol_system,
468 protocol_type_name: value.protocol_type_name,
469 chain: value.chain.into(),
470 tokens: value.tokens,
471 contract_ids: value.contract_addresses,
472 static_attributes: value.static_attributes,
473 change: value.change.into(),
474 creation_tx: value.creation_tx,
475 created_at: value.created_at,
476 }
477 }
478}
479
480#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
481pub struct ComponentBalance {
482 #[serde(with = "hex_bytes")]
483 pub token: Bytes,
484 pub balance: Bytes,
485 pub balance_float: f64,
486 #[serde(with = "hex_bytes")]
487 pub modify_tx: Bytes,
488 pub component_id: String,
489}
490
491#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, ToSchema)]
492pub struct ProtocolStateDelta {
494 pub component_id: String,
495 #[schema(value_type=HashMap<String, String>)]
496 pub updated_attributes: HashMap<String, Bytes>,
497 pub deleted_attributes: HashSet<String>,
498}
499
500impl From<models::protocol::ProtocolComponentStateDelta> for ProtocolStateDelta {
501 fn from(value: models::protocol::ProtocolComponentStateDelta) -> Self {
502 Self {
503 component_id: value.component_id,
504 updated_attributes: value.updated_attributes,
505 deleted_attributes: value.deleted_attributes,
506 }
507 }
508}
509
510impl ProtocolStateDelta {
511 pub fn merge(&mut self, other: &Self) {
530 self.updated_attributes
532 .retain(|k, _| !other.deleted_attributes.contains(k));
533
534 self.deleted_attributes.retain(|attr| {
536 !other
537 .updated_attributes
538 .contains_key(attr)
539 });
540
541 self.updated_attributes.extend(
543 other
544 .updated_attributes
545 .iter()
546 .map(|(k, v)| (k.clone(), v.clone())),
547 );
548
549 self.deleted_attributes
551 .extend(other.deleted_attributes.iter().cloned());
552 }
553}
554
555#[derive(Clone, Serialize, Debug, Default, Deserialize, PartialEq, ToSchema, Eq, Hash)]
557#[serde(deny_unknown_fields)]
558pub struct StateRequestBody {
559 #[serde(alias = "contractIds")]
561 #[schema(value_type=Option<Vec<String>>)]
562 pub contract_ids: Option<Vec<Bytes>>,
563 #[serde(alias = "protocolSystem", default)]
566 pub protocol_system: String,
567 #[serde(default = "VersionParam::default")]
568 pub version: VersionParam,
569 #[serde(default)]
570 pub chain: Chain,
571 #[serde(default)]
572 pub pagination: PaginationParams,
573}
574
575impl StateRequestBody {
576 pub fn new(
577 contract_ids: Option<Vec<Bytes>>,
578 protocol_system: String,
579 version: VersionParam,
580 chain: Chain,
581 pagination: PaginationParams,
582 ) -> Self {
583 Self { contract_ids, protocol_system, version, chain, pagination }
584 }
585
586 pub fn from_block(protocol_system: &str, block: BlockParam) -> Self {
587 Self {
588 contract_ids: None,
589 protocol_system: protocol_system.to_string(),
590 version: VersionParam { timestamp: None, block: Some(block.clone()) },
591 chain: block.chain.unwrap_or_default(),
592 pagination: PaginationParams::default(),
593 }
594 }
595
596 pub fn from_timestamp(protocol_system: &str, timestamp: NaiveDateTime, chain: Chain) -> Self {
597 Self {
598 contract_ids: None,
599 protocol_system: protocol_system.to_string(),
600 version: VersionParam { timestamp: Some(timestamp), block: None },
601 chain,
602 pagination: PaginationParams::default(),
603 }
604 }
605}
606
607#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema)]
609pub struct StateRequestResponse {
610 pub accounts: Vec<ResponseAccount>,
611 pub pagination: PaginationResponse,
612}
613
614impl StateRequestResponse {
615 pub fn new(accounts: Vec<ResponseAccount>, pagination: PaginationResponse) -> Self {
616 Self { accounts, pagination }
617 }
618}
619
620#[derive(PartialEq, Clone, Serialize, Deserialize, Default, ToSchema)]
621#[serde(rename = "Account")]
622pub struct ResponseAccount {
626 pub chain: Chain,
627 #[schema(value_type=String, example="0xc9f2e6ea1637E499406986ac50ddC92401ce1f58")]
629 #[serde(with = "hex_bytes")]
630 pub address: Bytes,
631 #[schema(value_type=String, example="Protocol Vault")]
633 pub title: String,
634 #[schema(value_type=HashMap<String, String>, example=json!({"0x....": "0x...."}))]
636 #[serde(with = "hex_hashmap_key_value")]
637 pub slots: HashMap<Bytes, Bytes>,
638 #[schema(value_type=String, example="0x00")]
640 #[serde(with = "hex_bytes")]
641 pub native_balance: Bytes,
642 #[schema(value_type=HashMap<String, String>, example=json!({"0x....": "0x...."}))]
645 #[serde(with = "hex_hashmap_key_value")]
646 pub token_balances: HashMap<Bytes, Bytes>,
647 #[schema(value_type=String, example="0xBADBABE")]
649 #[serde(with = "hex_bytes")]
650 pub code: Bytes,
651 #[schema(value_type=String, example="0x123456789")]
653 #[serde(with = "hex_bytes")]
654 pub code_hash: Bytes,
655 #[schema(value_type=String, example="0x8f1133bfb054a23aedfe5d25b1d81b96195396d8b88bd5d4bcf865fc1ae2c3f4")]
657 #[serde(with = "hex_bytes")]
658 pub balance_modify_tx: Bytes,
659 #[schema(value_type=String, example="0x8f1133bfb054a23aedfe5d25b1d81b96195396d8b88bd5d4bcf865fc1ae2c3f4")]
661 #[serde(with = "hex_bytes")]
662 pub code_modify_tx: Bytes,
663 #[deprecated(note = "The `creation_tx` field is deprecated.")]
665 #[schema(value_type=Option<String>, example="0x8f1133bfb054a23aedfe5d25b1d81b96195396d8b88bd5d4bcf865fc1ae2c3f4")]
666 #[serde(with = "hex_bytes_option")]
667 pub creation_tx: Option<Bytes>,
668}
669
670impl ResponseAccount {
671 #[allow(clippy::too_many_arguments)]
672 pub fn new(
673 chain: Chain,
674 address: Bytes,
675 title: String,
676 slots: HashMap<Bytes, Bytes>,
677 native_balance: Bytes,
678 token_balances: HashMap<Bytes, Bytes>,
679 code: Bytes,
680 code_hash: Bytes,
681 balance_modify_tx: Bytes,
682 code_modify_tx: Bytes,
683 creation_tx: Option<Bytes>,
684 ) -> Self {
685 Self {
686 chain,
687 address,
688 title,
689 slots,
690 native_balance,
691 token_balances,
692 code,
693 code_hash,
694 balance_modify_tx,
695 code_modify_tx,
696 creation_tx,
697 }
698 }
699}
700
701impl fmt::Debug for ResponseAccount {
703 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
704 f.debug_struct("ResponseAccount")
705 .field("chain", &self.chain)
706 .field("address", &self.address)
707 .field("title", &self.title)
708 .field("slots", &self.slots)
709 .field("native_balance", &self.native_balance)
710 .field("token_balances", &self.token_balances)
711 .field("code", &format!("[{} bytes]", self.code.len()))
712 .field("code_hash", &self.code_hash)
713 .field("balance_modify_tx", &self.balance_modify_tx)
714 .field("code_modify_tx", &self.code_modify_tx)
715 .field("creation_tx", &self.creation_tx)
716 .finish()
717 }
718}
719
720#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
721pub struct AccountBalance {
722 #[serde(with = "hex_bytes")]
723 pub account: Bytes,
724 #[serde(with = "hex_bytes")]
725 pub token: Bytes,
726 #[serde(with = "hex_bytes")]
727 pub balance: Bytes,
728 #[serde(with = "hex_bytes")]
729 pub modify_tx: Bytes,
730}
731
732#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
733#[serde(deny_unknown_fields)]
734pub struct ContractId {
735 #[serde(with = "hex_bytes")]
736 #[schema(value_type=String)]
737 pub address: Bytes,
738 pub chain: Chain,
739}
740
741impl ContractId {
743 pub fn new(chain: Chain, address: Bytes) -> Self {
744 Self { address, chain }
745 }
746
747 pub fn address(&self) -> &Bytes {
748 &self.address
749 }
750}
751
752impl fmt::Display for ContractId {
753 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
754 write!(f, "{:?}: 0x{}", self.chain, hex::encode(&self.address))
755 }
756}
757
758#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema, Eq, Hash)]
765#[serde(deny_unknown_fields)]
766pub struct VersionParam {
767 pub timestamp: Option<NaiveDateTime>,
768 pub block: Option<BlockParam>,
769}
770
771impl VersionParam {
772 pub fn new(timestamp: Option<NaiveDateTime>, block: Option<BlockParam>) -> Self {
773 Self { timestamp, block }
774 }
775}
776
777impl Default for VersionParam {
778 fn default() -> Self {
779 VersionParam { timestamp: Some(Utc::now().naive_utc()), block: None }
780 }
781}
782
783#[deprecated(note = "Use StateRequestBody instead")]
784#[derive(Serialize, Deserialize, Default, Debug, IntoParams)]
785pub struct StateRequestParameters {
786 #[param(default = 0)]
788 pub tvl_gt: Option<u64>,
789 #[param(default = 0)]
791 pub inertia_min_gt: Option<u64>,
792 #[serde(default = "default_include_balances_flag")]
794 pub include_balances: bool,
795 #[serde(default)]
796 pub pagination: PaginationParams,
797}
798
799impl StateRequestParameters {
800 pub fn new(include_balances: bool) -> Self {
801 Self {
802 tvl_gt: None,
803 inertia_min_gt: None,
804 include_balances,
805 pagination: PaginationParams::default(),
806 }
807 }
808
809 pub fn to_query_string(&self) -> String {
810 let mut parts = vec![format!("include_balances={}", self.include_balances)];
811
812 if let Some(tvl_gt) = self.tvl_gt {
813 parts.push(format!("tvl_gt={tvl_gt}"));
814 }
815
816 if let Some(inertia) = self.inertia_min_gt {
817 parts.push(format!("inertia_min_gt={inertia}"));
818 }
819
820 let mut res = parts.join("&");
821 if !res.is_empty() {
822 res = format!("?{res}");
823 }
824 res
825 }
826}
827
828#[derive(Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Hash, Clone)]
829#[serde(deny_unknown_fields)]
830pub struct TokensRequestBody {
831 #[serde(alias = "tokenAddresses")]
833 #[schema(value_type=Option<Vec<String>>)]
834 pub token_addresses: Option<Vec<Bytes>>,
835 #[serde(default)]
843 pub min_quality: Option<i32>,
844 #[serde(default)]
846 pub traded_n_days_ago: Option<u64>,
847 #[serde(default)]
849 pub pagination: PaginationParams,
850 #[serde(default)]
852 pub chain: Chain,
853}
854
855#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, Eq, Hash)]
857pub struct TokensRequestResponse {
858 pub tokens: Vec<ResponseToken>,
859 pub pagination: PaginationResponse,
860}
861
862impl TokensRequestResponse {
863 pub fn new(tokens: Vec<ResponseToken>, pagination_request: &PaginationResponse) -> Self {
864 Self { tokens, pagination: pagination_request.clone() }
865 }
866}
867
868#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, Eq, Hash)]
870#[serde(deny_unknown_fields)]
871pub struct PaginationParams {
872 #[serde(default)]
874 pub page: i64,
875 #[serde(default)]
877 #[schema(default = 10)]
878 pub page_size: i64,
879}
880
881impl PaginationParams {
882 pub fn new(page: i64, page_size: i64) -> Self {
883 Self { page, page_size }
884 }
885}
886
887impl Default for PaginationParams {
888 fn default() -> Self {
889 PaginationParams { page: 0, page_size: 20 }
890 }
891}
892
893#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, Eq, Hash)]
894#[serde(deny_unknown_fields)]
895pub struct PaginationResponse {
896 pub page: i64,
897 pub page_size: i64,
898 pub total: i64,
900}
901
902impl PaginationResponse {
904 pub fn new(page: i64, page_size: i64, total: i64) -> Self {
905 Self { page, page_size, total }
906 }
907
908 pub fn total_pages(&self) -> i64 {
909 (self.total + self.page_size - 1) / self.page_size
911 }
912}
913
914#[derive(PartialEq, Debug, Clone, Serialize, Deserialize, Default, ToSchema, Eq, Hash)]
915#[serde(rename = "Token")]
916pub struct ResponseToken {
918 pub chain: Chain,
919 #[schema(value_type=String, example="0xc9f2e6ea1637E499406986ac50ddC92401ce1f58")]
921 #[serde(with = "hex_bytes")]
922 pub address: Bytes,
923 #[schema(value_type=String, example="WETH")]
925 pub symbol: String,
926 pub decimals: u32,
928 pub tax: u64,
930 pub gas: Vec<Option<u64>>,
932 pub quality: u32,
940}
941
942impl From<models::token::Token> for ResponseToken {
943 fn from(value: models::token::Token) -> Self {
944 Self {
945 chain: value.chain.into(),
946 address: value.address,
947 symbol: value.symbol,
948 decimals: value.decimals,
949 tax: value.tax,
950 gas: value.gas,
951 quality: value.quality,
952 }
953 }
954}
955
956#[derive(Serialize, Deserialize, Debug, Default, ToSchema, Clone)]
957#[serde(deny_unknown_fields)]
958pub struct ProtocolComponentsRequestBody {
959 pub protocol_system: String,
962 #[serde(alias = "componentAddresses")]
964 pub component_ids: Option<Vec<ComponentId>>,
965 #[serde(default)]
968 pub tvl_gt: Option<f64>,
969 #[serde(default)]
970 pub chain: Chain,
971 #[serde(default)]
973 pub pagination: PaginationParams,
974}
975
976impl PartialEq for ProtocolComponentsRequestBody {
978 fn eq(&self, other: &Self) -> bool {
979 let tvl_close_enough = match (self.tvl_gt, other.tvl_gt) {
980 (Some(a), Some(b)) => (a - b).abs() < 1e-6,
981 (None, None) => true,
982 _ => false,
983 };
984
985 self.protocol_system == other.protocol_system &&
986 self.component_ids == other.component_ids &&
987 tvl_close_enough &&
988 self.chain == other.chain &&
989 self.pagination == other.pagination
990 }
991}
992
993impl Eq for ProtocolComponentsRequestBody {}
995
996impl Hash for ProtocolComponentsRequestBody {
997 fn hash<H: Hasher>(&self, state: &mut H) {
998 self.protocol_system.hash(state);
999 self.component_ids.hash(state);
1000
1001 if let Some(tvl) = self.tvl_gt {
1003 tvl.to_bits().hash(state);
1005 } else {
1006 state.write_u8(0);
1008 }
1009
1010 self.chain.hash(state);
1011 self.pagination.hash(state);
1012 }
1013}
1014
1015impl ProtocolComponentsRequestBody {
1016 pub fn system_filtered(system: &str, tvl_gt: Option<f64>, chain: Chain) -> Self {
1017 Self {
1018 protocol_system: system.to_string(),
1019 component_ids: None,
1020 tvl_gt,
1021 chain,
1022 pagination: Default::default(),
1023 }
1024 }
1025
1026 pub fn id_filtered(system: &str, ids: Vec<String>, chain: Chain) -> Self {
1027 Self {
1028 protocol_system: system.to_string(),
1029 component_ids: Some(ids),
1030 tvl_gt: None,
1031 chain,
1032 pagination: Default::default(),
1033 }
1034 }
1035}
1036
1037impl ProtocolComponentsRequestBody {
1038 pub fn new(
1039 protocol_system: String,
1040 component_ids: Option<Vec<String>>,
1041 tvl_gt: Option<f64>,
1042 chain: Chain,
1043 pagination: PaginationParams,
1044 ) -> Self {
1045 Self { protocol_system, component_ids, tvl_gt, chain, pagination }
1046 }
1047}
1048
1049#[deprecated(note = "Use ProtocolComponentsRequestBody instead")]
1050#[derive(Serialize, Deserialize, Default, Debug, IntoParams)]
1051pub struct ProtocolComponentRequestParameters {
1052 #[param(default = 0)]
1054 pub tvl_gt: Option<f64>,
1055}
1056
1057impl ProtocolComponentRequestParameters {
1058 pub fn tvl_filtered(min_tvl: f64) -> Self {
1059 Self { tvl_gt: Some(min_tvl) }
1060 }
1061}
1062
1063impl ProtocolComponentRequestParameters {
1064 pub fn to_query_string(&self) -> String {
1065 if let Some(tvl_gt) = self.tvl_gt {
1066 return format!("?tvl_gt={tvl_gt}");
1067 }
1068 String::new()
1069 }
1070}
1071
1072#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema)]
1074pub struct ProtocolComponentRequestResponse {
1075 pub protocol_components: Vec<ProtocolComponent>,
1076 pub pagination: PaginationResponse,
1077}
1078
1079impl ProtocolComponentRequestResponse {
1080 pub fn new(
1081 protocol_components: Vec<ProtocolComponent>,
1082 pagination: PaginationResponse,
1083 ) -> Self {
1084 Self { protocol_components, pagination }
1085 }
1086}
1087
1088#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema, Eq, Hash)]
1089#[serde(deny_unknown_fields)]
1090#[deprecated]
1091pub struct ProtocolId {
1092 pub id: String,
1093 pub chain: Chain,
1094}
1095
1096impl From<ProtocolId> for String {
1097 fn from(protocol_id: ProtocolId) -> Self {
1098 protocol_id.id
1099 }
1100}
1101
1102impl AsRef<str> for ProtocolId {
1103 fn as_ref(&self) -> &str {
1104 &self.id
1105 }
1106}
1107
1108#[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize, ToSchema)]
1110pub struct ResponseProtocolState {
1111 pub component_id: String,
1113 #[schema(value_type=HashMap<String, String>)]
1116 #[serde(with = "hex_hashmap_value")]
1117 pub attributes: HashMap<String, Bytes>,
1118 #[schema(value_type=HashMap<String, String>)]
1120 #[serde(with = "hex_hashmap_key_value")]
1121 pub balances: HashMap<Bytes, Bytes>,
1122}
1123
1124impl From<models::protocol::ProtocolComponentState> for ResponseProtocolState {
1125 fn from(value: models::protocol::ProtocolComponentState) -> Self {
1126 Self {
1127 component_id: value.component_id,
1128 attributes: value.attributes,
1129 balances: value.balances,
1130 }
1131 }
1132}
1133
1134fn default_include_balances_flag() -> bool {
1135 true
1136}
1137
1138#[derive(Clone, Debug, Serialize, PartialEq, ToSchema, Default, Eq, Hash)]
1140#[serde(deny_unknown_fields)]
1141pub struct ProtocolStateRequestBody {
1142 #[serde(alias = "protocolIds")]
1144 pub protocol_ids: Option<Vec<String>>,
1145 #[serde(alias = "protocolSystem")]
1148 pub protocol_system: String,
1149 #[serde(default)]
1150 pub chain: Chain,
1151 #[serde(default = "default_include_balances_flag")]
1153 pub include_balances: bool,
1154 #[serde(default = "VersionParam::default")]
1155 pub version: VersionParam,
1156 #[serde(default)]
1157 pub pagination: PaginationParams,
1158}
1159
1160impl ProtocolStateRequestBody {
1161 pub fn id_filtered<I, T>(ids: I) -> Self
1162 where
1163 I: IntoIterator<Item = T>,
1164 T: Into<String>,
1165 {
1166 Self {
1167 protocol_ids: Some(
1168 ids.into_iter()
1169 .map(Into::into)
1170 .collect(),
1171 ),
1172 ..Default::default()
1173 }
1174 }
1175}
1176
1177impl<'de> Deserialize<'de> for ProtocolStateRequestBody {
1181 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1182 where
1183 D: Deserializer<'de>,
1184 {
1185 #[derive(Deserialize)]
1186 #[serde(untagged)]
1187 enum ProtocolIdOrString {
1188 Old(Vec<ProtocolId>),
1189 New(Vec<String>),
1190 }
1191
1192 struct ProtocolStateRequestBodyVisitor;
1193
1194 impl<'de> de::Visitor<'de> for ProtocolStateRequestBodyVisitor {
1195 type Value = ProtocolStateRequestBody;
1196
1197 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1198 formatter.write_str("struct ProtocolStateRequestBody")
1199 }
1200
1201 fn visit_map<V>(self, mut map: V) -> Result<ProtocolStateRequestBody, V::Error>
1202 where
1203 V: de::MapAccess<'de>,
1204 {
1205 let mut protocol_ids = None;
1206 let mut protocol_system = None;
1207 let mut version = None;
1208 let mut chain = None;
1209 let mut include_balances = None;
1210 let mut pagination = None;
1211
1212 while let Some(key) = map.next_key::<String>()? {
1213 match key.as_str() {
1214 "protocol_ids" | "protocolIds" => {
1215 let value: ProtocolIdOrString = map.next_value()?;
1216 protocol_ids = match value {
1217 ProtocolIdOrString::Old(ids) => {
1218 Some(ids.into_iter().map(|p| p.id).collect())
1219 }
1220 ProtocolIdOrString::New(ids_str) => Some(ids_str),
1221 };
1222 }
1223 "protocol_system" | "protocolSystem" => {
1224 protocol_system = Some(map.next_value()?);
1225 }
1226 "version" => {
1227 version = Some(map.next_value()?);
1228 }
1229 "chain" => {
1230 chain = Some(map.next_value()?);
1231 }
1232 "include_balances" => {
1233 include_balances = Some(map.next_value()?);
1234 }
1235 "pagination" => {
1236 pagination = Some(map.next_value()?);
1237 }
1238 _ => {
1239 return Err(de::Error::unknown_field(
1240 &key,
1241 &[
1242 "contract_ids",
1243 "protocol_system",
1244 "version",
1245 "chain",
1246 "include_balances",
1247 "pagination",
1248 ],
1249 ))
1250 }
1251 }
1252 }
1253
1254 Ok(ProtocolStateRequestBody {
1255 protocol_ids,
1256 protocol_system: protocol_system.unwrap_or_default(),
1257 version: version.unwrap_or_else(VersionParam::default),
1258 chain: chain.unwrap_or_else(Chain::default),
1259 include_balances: include_balances.unwrap_or(true),
1260 pagination: pagination.unwrap_or_else(PaginationParams::default),
1261 })
1262 }
1263 }
1264
1265 deserializer.deserialize_struct(
1266 "ProtocolStateRequestBody",
1267 &[
1268 "contract_ids",
1269 "protocol_system",
1270 "version",
1271 "chain",
1272 "include_balances",
1273 "pagination",
1274 ],
1275 ProtocolStateRequestBodyVisitor,
1276 )
1277 }
1278}
1279
1280#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema)]
1281pub struct ProtocolStateRequestResponse {
1282 pub states: Vec<ResponseProtocolState>,
1283 pub pagination: PaginationResponse,
1284}
1285
1286impl ProtocolStateRequestResponse {
1287 pub fn new(states: Vec<ResponseProtocolState>, pagination: PaginationResponse) -> Self {
1288 Self { states, pagination }
1289 }
1290}
1291
1292#[derive(Serialize, Clone, PartialEq, Hash, Eq)]
1293pub struct ProtocolComponentId {
1294 pub chain: Chain,
1295 pub system: String,
1296 pub id: String,
1297}
1298
1299#[derive(Debug, Serialize, ToSchema)]
1300#[serde(tag = "status", content = "message")]
1301#[schema(example = json!({"status": "NotReady", "message": "No db connection"}))]
1302pub enum Health {
1303 Ready,
1304 Starting(String),
1305 NotReady(String),
1306}
1307
1308#[derive(Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Hash, Clone)]
1309#[serde(deny_unknown_fields)]
1310pub struct ProtocolSystemsRequestBody {
1311 #[serde(default)]
1312 pub chain: Chain,
1313 #[serde(default)]
1314 pub pagination: PaginationParams,
1315}
1316
1317#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema, Eq, Hash)]
1318pub struct ProtocolSystemsRequestResponse {
1319 pub protocol_systems: Vec<String>,
1321 pub pagination: PaginationResponse,
1322}
1323
1324impl ProtocolSystemsRequestResponse {
1325 pub fn new(protocol_systems: Vec<String>, pagination: PaginationResponse) -> Self {
1326 Self { protocol_systems, pagination }
1327 }
1328}
1329
1330#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
1331pub struct DCIUpdate {
1332 pub new_entrypoints: HashMap<ComponentId, HashSet<EntryPoint>>,
1334 pub new_entrypoint_params: HashMap<String, HashSet<(TracingParams, Option<String>)>>,
1337 pub trace_results: HashMap<String, TracingResult>,
1339}
1340
1341#[derive(Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Hash, Clone)]
1342#[serde(deny_unknown_fields)]
1343pub struct ComponentTvlRequestBody {
1344 #[serde(default)]
1345 pub chain: Chain,
1346 #[serde(alias = "protocolSystem")]
1349 pub protocol_system: Option<String>,
1350 #[serde(default)]
1351 pub component_ids: Option<Vec<String>>,
1352 #[serde(default)]
1353 pub pagination: PaginationParams,
1354}
1355
1356impl ComponentTvlRequestBody {
1357 pub fn system_filtered(system: &str, chain: Chain) -> Self {
1358 Self {
1359 chain,
1360 protocol_system: Some(system.to_string()),
1361 component_ids: None,
1362 pagination: Default::default(),
1363 }
1364 }
1365
1366 pub fn id_filtered(ids: Vec<String>, chain: Chain) -> Self {
1367 Self {
1368 chain,
1369 protocol_system: None,
1370 component_ids: Some(ids),
1371 pagination: Default::default(),
1372 }
1373 }
1374}
1375#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema)]
1377pub struct ComponentTvlRequestResponse {
1378 pub tvl: HashMap<String, f64>,
1379 pub pagination: PaginationResponse,
1380}
1381
1382impl ComponentTvlRequestResponse {
1383 pub fn new(tvl: HashMap<String, f64>, pagination: PaginationResponse) -> Self {
1384 Self { tvl, pagination }
1385 }
1386}
1387
1388#[derive(Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Hash, Clone)]
1389pub struct TracedEntryPointRequestBody {
1390 #[serde(default)]
1391 pub chain: Chain,
1392 pub protocol_system: String,
1395 pub component_ids: Option<Vec<ComponentId>>,
1397 #[serde(default)]
1399 pub pagination: PaginationParams,
1400}
1401
1402#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema, Eq, Hash)]
1403pub struct EntryPoint {
1404 #[schema(example = "0xEdf63cce4bA70cbE74064b7687882E71ebB0e988:getRate()")]
1405 pub external_id: String,
1407 #[schema(value_type=String, example="0x8f4E8439b970363648421C692dd897Fb9c0Bd1D9")]
1408 #[serde(with = "hex_bytes")]
1409 pub target: Bytes,
1411 #[schema(example = "getRate()")]
1412 pub signature: String,
1414}
1415
1416#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema, Eq, Hash)]
1417pub struct RPCTracerParams {
1418 #[schema(value_type=Option<String>)]
1421 #[serde(with = "hex_bytes_option", default)]
1422 pub caller: Option<Bytes>,
1423 #[schema(value_type=String, example="0x679aefce")]
1425 #[serde(with = "hex_bytes")]
1426 pub calldata: Bytes,
1427}
1428
1429impl From<models::blockchain::RPCTracerParams> for RPCTracerParams {
1430 fn from(value: models::blockchain::RPCTracerParams) -> Self {
1431 RPCTracerParams { caller: value.caller, calldata: value.calldata }
1432 }
1433}
1434
1435#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Hash)]
1436#[serde(tag = "method", rename_all = "lowercase")]
1437pub enum TracingParams {
1438 RPCTracer(RPCTracerParams),
1440}
1441
1442impl From<models::blockchain::TracingParams> for TracingParams {
1443 fn from(value: models::blockchain::TracingParams) -> Self {
1444 match value {
1445 models::blockchain::TracingParams::RPCTracer(params) => {
1446 TracingParams::RPCTracer(params.into())
1447 }
1448 }
1449 }
1450}
1451
1452impl From<models::blockchain::EntryPoint> for EntryPoint {
1453 fn from(value: models::blockchain::EntryPoint) -> Self {
1454 Self { external_id: value.external_id, target: value.target, signature: value.signature }
1455 }
1456}
1457
1458#[derive(Serialize, Deserialize, Debug, PartialEq, ToSchema, Eq, Clone)]
1459pub struct EntryPointWithTracingParams {
1460 pub entry_point: EntryPoint,
1462 pub params: TracingParams,
1464}
1465
1466impl From<models::blockchain::EntryPointWithTracingParams> for EntryPointWithTracingParams {
1467 fn from(value: models::blockchain::EntryPointWithTracingParams) -> Self {
1468 Self { entry_point: value.entry_point.into(), params: value.params.into() }
1469 }
1470}
1471
1472#[derive(Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Clone)]
1473pub struct TracingResult {
1474 #[schema(value_type=HashSet<(String, String)>)]
1475 pub retriggers: HashSet<(StoreKey, StoreVal)>,
1476 #[schema(value_type=HashMap<String,HashSet<String>>)]
1477 pub accessed_slots: HashMap<Address, HashSet<StoreKey>>,
1478}
1479
1480impl From<models::blockchain::TracingResult> for TracingResult {
1481 fn from(value: models::blockchain::TracingResult) -> Self {
1482 TracingResult { retriggers: value.retriggers, accessed_slots: value.accessed_slots }
1483 }
1484}
1485
1486#[derive(Serialize, PartialEq, ToSchema, Eq, Clone, Debug, Deserialize)]
1487pub struct TracedEntryPointRequestResponse {
1488 pub traced_entry_points:
1491 HashMap<ComponentId, Vec<(EntryPointWithTracingParams, TracingResult)>>,
1492 pub pagination: PaginationResponse,
1493}
1494
1495impl From<TracedEntryPointRequestResponse> for DCIUpdate {
1496 fn from(response: TracedEntryPointRequestResponse) -> Self {
1497 let mut new_entrypoints = HashMap::new();
1498 let mut new_entrypoint_params = HashMap::new();
1499 let mut trace_results = HashMap::new();
1500
1501 for (component, traces) in response.traced_entry_points {
1502 let mut entrypoints = HashSet::new();
1503
1504 for (entrypoint, trace) in traces {
1505 let entrypoint_id = entrypoint
1506 .entry_point
1507 .external_id
1508 .clone();
1509
1510 entrypoints.insert(entrypoint.entry_point.clone());
1512
1513 new_entrypoint_params
1515 .entry(entrypoint_id.clone())
1516 .or_insert_with(HashSet::new)
1517 .insert((entrypoint.params, Some(component.clone())));
1518
1519 trace_results
1521 .entry(entrypoint_id)
1522 .and_modify(|existing_trace: &mut TracingResult| {
1523 existing_trace
1525 .retriggers
1526 .extend(trace.retriggers.clone());
1527 for (address, slots) in trace.accessed_slots.clone() {
1528 existing_trace
1529 .accessed_slots
1530 .entry(address)
1531 .or_default()
1532 .extend(slots);
1533 }
1534 })
1535 .or_insert(trace);
1536 }
1537
1538 if !entrypoints.is_empty() {
1539 new_entrypoints.insert(component, entrypoints);
1540 }
1541 }
1542
1543 DCIUpdate { new_entrypoints, new_entrypoint_params, trace_results }
1544 }
1545}
1546
1547#[derive(Serialize, Deserialize, Debug, Default, PartialEq, ToSchema, Eq, Clone)]
1548pub struct AddEntryPointRequestBody {
1549 #[serde(default)]
1550 pub chain: Chain,
1551 #[schema(value_type=String)]
1552 #[serde(default)]
1553 pub block_hash: Bytes,
1554 pub entry_points_with_tracing_data: Vec<(ComponentId, Vec<EntryPointWithTracingParams>)>,
1556}
1557
1558#[derive(Serialize, PartialEq, ToSchema, Eq, Clone, Debug, Deserialize)]
1559pub struct AddEntryPointRequestResponse {
1560 pub traced_entry_points:
1563 HashMap<ComponentId, Vec<(EntryPointWithTracingParams, TracingResult)>>,
1564}
1565
1566#[cfg(test)]
1567mod test {
1568 use std::str::FromStr;
1569
1570 use maplit::hashmap;
1571 use rstest::rstest;
1572
1573 use super::*;
1574
1575 #[test]
1576 fn test_protocol_components_equality() {
1577 let body1 = ProtocolComponentsRequestBody {
1578 protocol_system: "protocol1".to_string(),
1579 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
1580 tvl_gt: Some(1000.0),
1581 chain: Chain::Ethereum,
1582 pagination: PaginationParams::default(),
1583 };
1584
1585 let body2 = ProtocolComponentsRequestBody {
1586 protocol_system: "protocol1".to_string(),
1587 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
1588 tvl_gt: Some(1000.0 + 1e-7), chain: Chain::Ethereum,
1590 pagination: PaginationParams::default(),
1591 };
1592
1593 assert_eq!(body1, body2);
1595 }
1596
1597 #[test]
1598 fn test_protocol_components_inequality() {
1599 let body1 = ProtocolComponentsRequestBody {
1600 protocol_system: "protocol1".to_string(),
1601 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
1602 tvl_gt: Some(1000.0),
1603 chain: Chain::Ethereum,
1604 pagination: PaginationParams::default(),
1605 };
1606
1607 let body2 = ProtocolComponentsRequestBody {
1608 protocol_system: "protocol1".to_string(),
1609 component_ids: Some(vec!["component1".to_string(), "component2".to_string()]),
1610 tvl_gt: Some(1000.0 + 1e-5), chain: Chain::Ethereum,
1612 pagination: PaginationParams::default(),
1613 };
1614
1615 assert_ne!(body1, body2);
1617 }
1618
1619 #[test]
1620 fn test_parse_state_request() {
1621 let json_str = r#"
1622 {
1623 "contractIds": [
1624 "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092"
1625 ],
1626 "protocol_system": "uniswap_v2",
1627 "version": {
1628 "timestamp": "2069-01-01T04:20:00",
1629 "block": {
1630 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
1631 "number": 213,
1632 "chain": "ethereum"
1633 }
1634 }
1635 }
1636 "#;
1637
1638 let result: StateRequestBody = serde_json::from_str(json_str).unwrap();
1639
1640 let contract0 = "b4eccE46b8D4e4abFd03C9B806276A6735C9c092"
1641 .parse()
1642 .unwrap();
1643 let block_hash = "24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4"
1644 .parse()
1645 .unwrap();
1646 let block_number = 213;
1647
1648 let expected_timestamp =
1649 NaiveDateTime::parse_from_str("2069-01-01T04:20:00", "%Y-%m-%dT%H:%M:%S").unwrap();
1650
1651 let expected = StateRequestBody {
1652 contract_ids: Some(vec![contract0]),
1653 protocol_system: "uniswap_v2".to_string(),
1654 version: VersionParam {
1655 timestamp: Some(expected_timestamp),
1656 block: Some(BlockParam {
1657 hash: Some(block_hash),
1658 chain: Some(Chain::Ethereum),
1659 number: Some(block_number),
1660 }),
1661 },
1662 chain: Chain::Ethereum,
1663 pagination: PaginationParams::default(),
1664 };
1665
1666 assert_eq!(result, expected);
1667 }
1668
1669 #[test]
1670 fn test_parse_state_request_dual_interface() {
1671 let json_common = r#"
1672 {
1673 "__CONTRACT_IDS__": [
1674 "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092"
1675 ],
1676 "version": {
1677 "timestamp": "2069-01-01T04:20:00",
1678 "block": {
1679 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
1680 "number": 213,
1681 "chain": "ethereum"
1682 }
1683 }
1684 }
1685 "#;
1686
1687 let json_str_snake = json_common.replace("\"__CONTRACT_IDS__\"", "\"contract_ids\"");
1688 let json_str_camel = json_common.replace("\"__CONTRACT_IDS__\"", "\"contractIds\"");
1689
1690 let snake: StateRequestBody = serde_json::from_str(&json_str_snake).unwrap();
1691 let camel: StateRequestBody = serde_json::from_str(&json_str_camel).unwrap();
1692
1693 assert_eq!(snake, camel);
1694 }
1695
1696 #[test]
1697 fn test_parse_state_request_unknown_field() {
1698 let body = r#"
1699 {
1700 "contract_ids_with_typo_error": [
1701 {
1702 "address": "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092",
1703 "chain": "ethereum"
1704 }
1705 ],
1706 "version": {
1707 "timestamp": "2069-01-01T04:20:00",
1708 "block": {
1709 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
1710 "parentHash": "0x8d75152454e60413efe758cc424bfd339897062d7e658f302765eb7b50971815",
1711 "number": 213,
1712 "chain": "ethereum"
1713 }
1714 }
1715 }
1716 "#;
1717
1718 let decoded = serde_json::from_str::<StateRequestBody>(body);
1719
1720 assert!(decoded.is_err(), "Expected an error due to unknown field");
1721
1722 if let Err(e) = decoded {
1723 assert!(
1724 e.to_string()
1725 .contains("unknown field `contract_ids_with_typo_error`"),
1726 "Error message does not contain expected unknown field information"
1727 );
1728 }
1729 }
1730
1731 #[test]
1732 fn test_parse_state_request_no_contract_specified() {
1733 let json_str = r#"
1734 {
1735 "protocol_system": "uniswap_v2",
1736 "version": {
1737 "timestamp": "2069-01-01T04:20:00",
1738 "block": {
1739 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
1740 "number": 213,
1741 "chain": "ethereum"
1742 }
1743 }
1744 }
1745 "#;
1746
1747 let result: StateRequestBody = serde_json::from_str(json_str).unwrap();
1748
1749 let block_hash = "24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4".into();
1750 let block_number = 213;
1751 let expected_timestamp =
1752 NaiveDateTime::parse_from_str("2069-01-01T04:20:00", "%Y-%m-%dT%H:%M:%S").unwrap();
1753
1754 let expected = StateRequestBody {
1755 contract_ids: None,
1756 protocol_system: "uniswap_v2".to_string(),
1757 version: VersionParam {
1758 timestamp: Some(expected_timestamp),
1759 block: Some(BlockParam {
1760 hash: Some(block_hash),
1761 chain: Some(Chain::Ethereum),
1762 number: Some(block_number),
1763 }),
1764 },
1765 chain: Chain::Ethereum,
1766 pagination: PaginationParams { page: 0, page_size: 20 },
1767 };
1768
1769 assert_eq!(result, expected);
1770 }
1771
1772 #[rstest]
1773 #[case::deprecated_ids(
1774 r#"
1775 {
1776 "protocol_ids": [
1777 {
1778 "id": "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092",
1779 "chain": "ethereum"
1780 }
1781 ],
1782 "protocol_system": "uniswap_v2",
1783 "include_balances": false,
1784 "version": {
1785 "timestamp": "2069-01-01T04:20:00",
1786 "block": {
1787 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
1788 "number": 213,
1789 "chain": "ethereum"
1790 }
1791 }
1792 }
1793 "#
1794 )]
1795 #[case(
1796 r#"
1797 {
1798 "protocolIds": [
1799 "0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092"
1800 ],
1801 "protocol_system": "uniswap_v2",
1802 "include_balances": false,
1803 "version": {
1804 "timestamp": "2069-01-01T04:20:00",
1805 "block": {
1806 "hash": "0x24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4",
1807 "number": 213,
1808 "chain": "ethereum"
1809 }
1810 }
1811 }
1812 "#
1813 )]
1814 fn test_parse_protocol_state_request(#[case] json_str: &str) {
1815 let result: ProtocolStateRequestBody = serde_json::from_str(json_str).unwrap();
1816
1817 let block_hash = "24101f9cb26cd09425b52da10e8c2f56ede94089a8bbe0f31f1cda5f4daa52c4"
1818 .parse()
1819 .unwrap();
1820 let block_number = 213;
1821
1822 let expected_timestamp =
1823 NaiveDateTime::parse_from_str("2069-01-01T04:20:00", "%Y-%m-%dT%H:%M:%S").unwrap();
1824
1825 let expected = ProtocolStateRequestBody {
1826 protocol_ids: Some(vec!["0xb4eccE46b8D4e4abFd03C9B806276A6735C9c092".to_string()]),
1827 protocol_system: "uniswap_v2".to_string(),
1828 version: VersionParam {
1829 timestamp: Some(expected_timestamp),
1830 block: Some(BlockParam {
1831 hash: Some(block_hash),
1832 chain: Some(Chain::Ethereum),
1833 number: Some(block_number),
1834 }),
1835 },
1836 chain: Chain::Ethereum,
1837 include_balances: false,
1838 pagination: PaginationParams::default(),
1839 };
1840
1841 assert_eq!(result, expected);
1842 }
1843
1844 #[rstest]
1845 #[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()])]
1846 #[case::with_strings(vec!["id1".to_string(), "id2".to_string()], vec!["id1".to_string(), "id2".to_string()])]
1847 fn test_id_filtered<T>(#[case] input_ids: Vec<T>, #[case] expected_ids: Vec<String>)
1848 where
1849 T: Into<String> + Clone,
1850 {
1851 let request_body = ProtocolStateRequestBody::id_filtered(input_ids);
1852
1853 assert_eq!(request_body.protocol_ids, Some(expected_ids));
1854 }
1855
1856 fn create_models_block_changes() -> crate::models::blockchain::BlockAggregatedChanges {
1857 let base_ts = 1694534400; crate::models::blockchain::BlockAggregatedChanges {
1860 extractor: "native_name".to_string(),
1861 block: models::blockchain::Block::new(
1862 3,
1863 models::Chain::Ethereum,
1864 Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000003").unwrap(),
1865 Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000002").unwrap(),
1866 NaiveDateTime::from_timestamp_opt(base_ts + 3000, 0).unwrap(),
1867 ),
1868 finalized_block_height: 1,
1869 revert: true,
1870 state_deltas: HashMap::from([
1871 ("pc_1".to_string(), models::protocol::ProtocolComponentStateDelta {
1872 component_id: "pc_1".to_string(),
1873 updated_attributes: HashMap::from([
1874 ("attr_2".to_string(), Bytes::from("0x0000000000000002")),
1875 ("attr_1".to_string(), Bytes::from("0x00000000000003e8")),
1876 ]),
1877 deleted_attributes: HashSet::new(),
1878 }),
1879 ]),
1880 new_protocol_components: HashMap::from([
1881 ("pc_2".to_string(), crate::models::protocol::ProtocolComponent {
1882 id: "pc_2".to_string(),
1883 protocol_system: "native_protocol_system".to_string(),
1884 protocol_type_name: "pt_1".to_string(),
1885 chain: models::Chain::Ethereum,
1886 tokens: vec![
1887 Bytes::from_str("0xdac17f958d2ee523a2206206994597c13d831ec7").unwrap(),
1888 Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
1889 ],
1890 contract_addresses: vec![],
1891 static_attributes: HashMap::new(),
1892 change: models::ChangeType::Creation,
1893 creation_tx: Bytes::from_str("0x000000000000000000000000000000000000000000000000000000000000c351").unwrap(),
1894 created_at: NaiveDateTime::from_timestamp_opt(base_ts + 5000, 0).unwrap(),
1895 }),
1896 ]),
1897 deleted_protocol_components: HashMap::from([
1898 ("pc_3".to_string(), crate::models::protocol::ProtocolComponent {
1899 id: "pc_3".to_string(),
1900 protocol_system: "native_protocol_system".to_string(),
1901 protocol_type_name: "pt_2".to_string(),
1902 chain: models::Chain::Ethereum,
1903 tokens: vec![
1904 Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(),
1905 Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(),
1906 ],
1907 contract_addresses: vec![],
1908 static_attributes: HashMap::new(),
1909 change: models::ChangeType::Deletion,
1910 creation_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000009c41").unwrap(),
1911 created_at: NaiveDateTime::from_timestamp_opt(base_ts + 4000, 0).unwrap(),
1912 }),
1913 ]),
1914 component_balances: HashMap::from([
1915 ("pc_1".to_string(), HashMap::from([
1916 (Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(), models::protocol::ComponentBalance {
1917 token: Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(),
1918 balance: Bytes::from("0x00000001"),
1919 balance_float: 1.0,
1920 modify_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
1921 component_id: "pc_1".to_string(),
1922 }),
1923 (Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(), models::protocol::ComponentBalance {
1924 token: Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(),
1925 balance: Bytes::from("0x000003e8"),
1926 balance_float: 1000.0,
1927 modify_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000007531").unwrap(),
1928 component_id: "pc_1".to_string(),
1929 }),
1930 ])),
1931 ]),
1932 account_balances: HashMap::from([
1933 (Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(), HashMap::from([
1934 (Bytes::from_str("0x7a250d5630b4cf539739df2c5dacb4c659f2488d").unwrap(), models::contract::AccountBalance {
1935 account: Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(),
1936 token: Bytes::from_str("0x7a250d5630b4cf539739df2c5dacb4c659f2488d").unwrap(),
1937 balance: Bytes::from("0x000003e8"),
1938 modify_tx: Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000007531").unwrap(),
1939 }),
1940 ])),
1941 ]),
1942 ..Default::default()
1943 }
1944 }
1945
1946 #[test]
1947 fn test_serialize_deserialize_block_changes() {
1948 let block_entity_changes = create_models_block_changes();
1953
1954 let json_data = serde_json::to_string(&block_entity_changes).expect("Failed to serialize");
1956
1957 serde_json::from_str::<BlockChanges>(&json_data).expect("parsing failed");
1959 }
1960
1961 #[test]
1962 fn test_parse_block_changes() {
1963 let json_data = r#"
1964 {
1965 "extractor": "vm:ambient",
1966 "chain": "ethereum",
1967 "block": {
1968 "number": 123,
1969 "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
1970 "parent_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
1971 "chain": "ethereum",
1972 "ts": "2023-09-14T00:00:00"
1973 },
1974 "finalized_block_height": 0,
1975 "revert": false,
1976 "new_tokens": {},
1977 "account_updates": {
1978 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
1979 "address": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
1980 "chain": "ethereum",
1981 "slots": {},
1982 "balance": "0x01f4",
1983 "code": "",
1984 "change": "Update"
1985 }
1986 },
1987 "state_updates": {
1988 "component_1": {
1989 "component_id": "component_1",
1990 "updated_attributes": {"attr1": "0x01"},
1991 "deleted_attributes": ["attr2"]
1992 }
1993 },
1994 "new_protocol_components":
1995 { "protocol_1": {
1996 "id": "protocol_1",
1997 "protocol_system": "system_1",
1998 "protocol_type_name": "type_1",
1999 "chain": "ethereum",
2000 "tokens": ["0x01", "0x02"],
2001 "contract_ids": ["0x01", "0x02"],
2002 "static_attributes": {"attr1": "0x01f4"},
2003 "change": "Update",
2004 "creation_tx": "0x01",
2005 "created_at": "2023-09-14T00:00:00"
2006 }
2007 },
2008 "deleted_protocol_components": {},
2009 "component_balances": {
2010 "protocol_1":
2011 {
2012 "0x01": {
2013 "token": "0x01",
2014 "balance": "0xb77831d23691653a01",
2015 "balance_float": 3.3844151001790677e21,
2016 "modify_tx": "0x01",
2017 "component_id": "protocol_1"
2018 }
2019 }
2020 },
2021 "account_balances": {
2022 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2023 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2024 "account": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2025 "token": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2026 "balance": "0x01f4",
2027 "modify_tx": "0x01"
2028 }
2029 }
2030 },
2031 "component_tvl": {
2032 "protocol_1": 1000.0
2033 },
2034 "dci_update": {
2035 "new_entrypoints": {
2036 "component_1": [
2037 {
2038 "external_id": "0x01:sig()",
2039 "target": "0x01",
2040 "signature": "sig()"
2041 }
2042 ]
2043 },
2044 "new_entrypoint_params": {
2045 "0x01:sig()": [
2046 [
2047 {
2048 "method": "rpctracer",
2049 "caller": "0x01",
2050 "calldata": "0x02"
2051 },
2052 "component_1"
2053 ]
2054 ]
2055 },
2056 "trace_results": {
2057 "0x01:sig()": {
2058 "retriggers": [
2059 ["0x01", "0x02"]
2060 ],
2061 "accessed_slots": {
2062 "0x03": ["0x03", "0x04"]
2063 }
2064 }
2065 }
2066 }
2067 }
2068 "#;
2069
2070 serde_json::from_str::<BlockChanges>(json_data).expect("parsing failed");
2071 }
2072
2073 #[test]
2074 fn test_parse_websocket_message() {
2075 let json_data = r#"
2076 {
2077 "subscription_id": "5d23bfbe-89ad-4ea3-8672-dc9e973ac9dc",
2078 "deltas": {
2079 "type": "BlockChanges",
2080 "extractor": "uniswap_v2",
2081 "chain": "ethereum",
2082 "block": {
2083 "number": 19291517,
2084 "hash": "0xbc3ea4896c0be8da6229387a8571b72818aa258daf4fab46471003ad74c4ee83",
2085 "parent_hash": "0x89ca5b8d593574cf6c886f41ef8208bf6bdc1a90ef36046cb8c84bc880b9af8f",
2086 "chain": "ethereum",
2087 "ts": "2024-02-23T16:35:35"
2088 },
2089 "finalized_block_height": 0,
2090 "revert": false,
2091 "new_tokens": {},
2092 "account_updates": {
2093 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2094 "address": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2095 "chain": "ethereum",
2096 "slots": {},
2097 "balance": "0x01f4",
2098 "code": "",
2099 "change": "Update"
2100 }
2101 },
2102 "state_updates": {
2103 "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28": {
2104 "component_id": "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28",
2105 "updated_attributes": {
2106 "reserve0": "0x87f7b5973a7f28a8b32404",
2107 "reserve1": "0x09e9564b11"
2108 },
2109 "deleted_attributes": []
2110 },
2111 "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d": {
2112 "component_id": "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d",
2113 "updated_attributes": {
2114 "reserve1": "0x44d9a8fd662c2f4d03",
2115 "reserve0": "0x500b1261f811d5bf423e"
2116 },
2117 "deleted_attributes": []
2118 }
2119 },
2120 "new_protocol_components": {},
2121 "deleted_protocol_components": {},
2122 "component_balances": {
2123 "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d": {
2124 "0x9012744b7a564623b6c3e40b144fc196bdedf1a9": {
2125 "token": "0x9012744b7a564623b6c3e40b144fc196bdedf1a9",
2126 "balance": "0x500b1261f811d5bf423e",
2127 "balance_float": 3.779935574269033E23,
2128 "modify_tx": "0xe46c4db085fb6c6f3408a65524555797adb264e1d5cf3b66ad154598f85ac4bf",
2129 "component_id": "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d"
2130 },
2131 "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": {
2132 "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
2133 "balance": "0x44d9a8fd662c2f4d03",
2134 "balance_float": 1.270062661329837E21,
2135 "modify_tx": "0xe46c4db085fb6c6f3408a65524555797adb264e1d5cf3b66ad154598f85ac4bf",
2136 "component_id": "0x99c59000f5a76c54c4fd7d82720c045bdcf1450d"
2137 }
2138 }
2139 },
2140 "account_balances": {
2141 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2142 "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
2143 "account": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2144 "token": "0x7a250d5630b4cf539739df2c5dacb4c659f2488d",
2145 "balance": "0x01f4",
2146 "modify_tx": "0x01"
2147 }
2148 }
2149 },
2150 "component_tvl": {},
2151 "dci_update": {
2152 "new_entrypoints": {
2153 "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28": [
2154 {
2155 "external_id": "0x01:sig()",
2156 "target": "0x01",
2157 "signature": "sig()"
2158 }
2159 ]
2160 },
2161 "new_entrypoint_params": {
2162 "0x01:sig()": [
2163 [
2164 {
2165 "method": "rpctracer",
2166 "caller": "0x01",
2167 "calldata": "0x02"
2168 },
2169 "0xde6faedbcae38eec6d33ad61473a04a6dd7f6e28"
2170 ]
2171 ]
2172 },
2173 "trace_results": {
2174 "0x01:sig()": {
2175 "retriggers": [
2176 ["0x01", "0x02"]
2177 ],
2178 "accessed_slots": {
2179 "0x03": ["0x03", "0x04"]
2180 }
2181 }
2182 }
2183 }
2184 }
2185 }
2186 "#;
2187 serde_json::from_str::<WebSocketMessage>(json_data).expect("parsing failed");
2188 }
2189
2190 #[test]
2191 fn test_protocol_state_delta_merge_update_delete() {
2192 let mut delta1 = ProtocolStateDelta {
2194 component_id: "Component1".to_string(),
2195 updated_attributes: HashMap::from([(
2196 "Attribute1".to_string(),
2197 Bytes::from("0xbadbabe420"),
2198 )]),
2199 deleted_attributes: HashSet::new(),
2200 };
2201 let delta2 = ProtocolStateDelta {
2202 component_id: "Component1".to_string(),
2203 updated_attributes: HashMap::from([(
2204 "Attribute2".to_string(),
2205 Bytes::from("0x0badbabe"),
2206 )]),
2207 deleted_attributes: HashSet::from(["Attribute1".to_string()]),
2208 };
2209 let exp = ProtocolStateDelta {
2210 component_id: "Component1".to_string(),
2211 updated_attributes: HashMap::from([(
2212 "Attribute2".to_string(),
2213 Bytes::from("0x0badbabe"),
2214 )]),
2215 deleted_attributes: HashSet::from(["Attribute1".to_string()]),
2216 };
2217
2218 delta1.merge(&delta2);
2219
2220 assert_eq!(delta1, exp);
2221 }
2222
2223 #[test]
2224 fn test_protocol_state_delta_merge_delete_update() {
2225 let mut delta1 = ProtocolStateDelta {
2227 component_id: "Component1".to_string(),
2228 updated_attributes: HashMap::new(),
2229 deleted_attributes: HashSet::from(["Attribute1".to_string()]),
2230 };
2231 let delta2 = ProtocolStateDelta {
2232 component_id: "Component1".to_string(),
2233 updated_attributes: HashMap::from([(
2234 "Attribute1".to_string(),
2235 Bytes::from("0x0badbabe"),
2236 )]),
2237 deleted_attributes: HashSet::new(),
2238 };
2239 let exp = ProtocolStateDelta {
2240 component_id: "Component1".to_string(),
2241 updated_attributes: HashMap::from([(
2242 "Attribute1".to_string(),
2243 Bytes::from("0x0badbabe"),
2244 )]),
2245 deleted_attributes: HashSet::new(),
2246 };
2247
2248 delta1.merge(&delta2);
2249
2250 assert_eq!(delta1, exp);
2251 }
2252
2253 #[test]
2254 fn test_account_update_merge() {
2255 let mut account1 = AccountUpdate::new(
2257 Bytes::from(b"0x1234"),
2258 Chain::Ethereum,
2259 HashMap::from([(Bytes::from("0xaabb"), Bytes::from("0xccdd"))]),
2260 Some(Bytes::from("0x1000")),
2261 Some(Bytes::from("0xdeadbeaf")),
2262 ChangeType::Creation,
2263 );
2264
2265 let account2 = AccountUpdate::new(
2266 Bytes::from(b"0x1234"), Chain::Ethereum,
2268 HashMap::from([(Bytes::from("0xeeff"), Bytes::from("0x11223344"))]),
2269 Some(Bytes::from("0x2000")),
2270 Some(Bytes::from("0xcafebabe")),
2271 ChangeType::Update,
2272 );
2273
2274 account1.merge(&account2);
2276
2277 let expected = AccountUpdate::new(
2279 Bytes::from(b"0x1234"), Chain::Ethereum,
2281 HashMap::from([
2282 (Bytes::from("0xaabb"), Bytes::from("0xccdd")), (Bytes::from("0xeeff"), Bytes::from("0x11223344")), ]),
2285 Some(Bytes::from("0x2000")), Some(Bytes::from("0xcafebabe")), ChangeType::Creation, );
2289
2290 assert_eq!(account1, expected);
2292 }
2293
2294 #[test]
2295 fn test_block_account_changes_merge() {
2296 let old_account_updates: HashMap<Bytes, AccountUpdate> = [(
2298 Bytes::from("0x0011"),
2299 AccountUpdate {
2300 address: Bytes::from("0x00"),
2301 chain: Chain::Ethereum,
2302 slots: HashMap::from([(Bytes::from("0x0022"), Bytes::from("0x0033"))]),
2303 balance: Some(Bytes::from("0x01")),
2304 code: Some(Bytes::from("0x02")),
2305 change: ChangeType::Creation,
2306 },
2307 )]
2308 .into_iter()
2309 .collect();
2310 let new_account_updates: HashMap<Bytes, AccountUpdate> = [(
2311 Bytes::from("0x0011"),
2312 AccountUpdate {
2313 address: Bytes::from("0x00"),
2314 chain: Chain::Ethereum,
2315 slots: HashMap::from([(Bytes::from("0x0044"), Bytes::from("0x0055"))]),
2316 balance: Some(Bytes::from("0x03")),
2317 code: Some(Bytes::from("0x04")),
2318 change: ChangeType::Update,
2319 },
2320 )]
2321 .into_iter()
2322 .collect();
2323 let block_account_changes_initial = BlockChanges {
2325 extractor: "extractor1".to_string(),
2326 revert: false,
2327 account_updates: old_account_updates,
2328 ..Default::default()
2329 };
2330
2331 let block_account_changes_new = BlockChanges {
2332 extractor: "extractor2".to_string(),
2333 revert: true,
2334 account_updates: new_account_updates,
2335 ..Default::default()
2336 };
2337
2338 let res = block_account_changes_initial.merge(block_account_changes_new);
2340
2341 let expected_account_updates: HashMap<Bytes, AccountUpdate> = [(
2343 Bytes::from("0x0011"),
2344 AccountUpdate {
2345 address: Bytes::from("0x00"),
2346 chain: Chain::Ethereum,
2347 slots: HashMap::from([
2348 (Bytes::from("0x0044"), Bytes::from("0x0055")),
2349 (Bytes::from("0x0022"), Bytes::from("0x0033")),
2350 ]),
2351 balance: Some(Bytes::from("0x03")),
2352 code: Some(Bytes::from("0x04")),
2353 change: ChangeType::Creation,
2354 },
2355 )]
2356 .into_iter()
2357 .collect();
2358 let block_account_changes_expected = BlockChanges {
2359 extractor: "extractor1".to_string(),
2360 revert: true,
2361 account_updates: expected_account_updates,
2362 ..Default::default()
2363 };
2364 assert_eq!(res, block_account_changes_expected);
2365 }
2366
2367 #[test]
2368 fn test_block_entity_changes_merge() {
2369 let block_entity_changes_result1 = BlockChanges {
2371 extractor: String::from("extractor1"),
2372 revert: false,
2373 state_updates: hashmap! { "state1".to_string() => ProtocolStateDelta::default() },
2374 new_protocol_components: hashmap! { "component1".to_string() => ProtocolComponent::default() },
2375 deleted_protocol_components: HashMap::new(),
2376 component_balances: hashmap! {
2377 "component1".to_string() => TokenBalances(hashmap! {
2378 Bytes::from("0x01") => ComponentBalance {
2379 token: Bytes::from("0x01"),
2380 balance: Bytes::from("0x01"),
2381 balance_float: 1.0,
2382 modify_tx: Bytes::from("0x00"),
2383 component_id: "component1".to_string()
2384 },
2385 Bytes::from("0x02") => ComponentBalance {
2386 token: Bytes::from("0x02"),
2387 balance: Bytes::from("0x02"),
2388 balance_float: 2.0,
2389 modify_tx: Bytes::from("0x00"),
2390 component_id: "component1".to_string()
2391 },
2392 })
2393
2394 },
2395 component_tvl: hashmap! { "tvl1".to_string() => 1000.0 },
2396 ..Default::default()
2397 };
2398 let block_entity_changes_result2 = BlockChanges {
2399 extractor: String::from("extractor2"),
2400 revert: true,
2401 state_updates: hashmap! { "state2".to_string() => ProtocolStateDelta::default() },
2402 new_protocol_components: hashmap! { "component2".to_string() => ProtocolComponent::default() },
2403 deleted_protocol_components: hashmap! { "component3".to_string() => ProtocolComponent::default() },
2404 component_balances: hashmap! {
2405 "component1".to_string() => TokenBalances::default(),
2406 "component2".to_string() => TokenBalances::default()
2407 },
2408 component_tvl: hashmap! { "tvl2".to_string() => 2000.0 },
2409 ..Default::default()
2410 };
2411
2412 let res = block_entity_changes_result1.merge(block_entity_changes_result2);
2413
2414 let expected_block_entity_changes_result = BlockChanges {
2415 extractor: String::from("extractor1"),
2416 revert: true,
2417 state_updates: hashmap! {
2418 "state1".to_string() => ProtocolStateDelta::default(),
2419 "state2".to_string() => ProtocolStateDelta::default(),
2420 },
2421 new_protocol_components: hashmap! {
2422 "component1".to_string() => ProtocolComponent::default(),
2423 "component2".to_string() => ProtocolComponent::default(),
2424 },
2425 deleted_protocol_components: hashmap! {
2426 "component3".to_string() => ProtocolComponent::default(),
2427 },
2428 component_balances: hashmap! {
2429 "component1".to_string() => TokenBalances(hashmap! {
2430 Bytes::from("0x01") => ComponentBalance {
2431 token: Bytes::from("0x01"),
2432 balance: Bytes::from("0x01"),
2433 balance_float: 1.0,
2434 modify_tx: Bytes::from("0x00"),
2435 component_id: "component1".to_string()
2436 },
2437 Bytes::from("0x02") => ComponentBalance {
2438 token: Bytes::from("0x02"),
2439 balance: Bytes::from("0x02"),
2440 balance_float: 2.0,
2441 modify_tx: Bytes::from("0x00"),
2442 component_id: "component1".to_string()
2443 },
2444 }),
2445 "component2".to_string() => TokenBalances::default(),
2446 },
2447 component_tvl: hashmap! {
2448 "tvl1".to_string() => 1000.0,
2449 "tvl2".to_string() => 2000.0
2450 },
2451 ..Default::default()
2452 };
2453
2454 assert_eq!(res, expected_block_entity_changes_result);
2455 }
2456}