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