1mod account;
19mod event;
20mod exchange;
21mod fee;
22mod l3_book;
23mod order;
24mod perpetual;
25mod position;
26mod version;
27
28use std::collections::{HashMap, hash_map};
29
30pub use account::*;
31use alloy::{
32 eips::BlockId,
33 primitives::U256,
34 providers::{CallItem, Provider},
35};
36pub use event::*;
37pub use exchange::*;
38use fastnum::UD64;
39pub use fee::*;
40use itertools::Itertools;
41pub use l3_book::*;
42pub use order::*;
43pub use perpetual::*;
44pub use position::*;
45pub use version::*;
46
47use crate::{
48 Chain,
49 abi::dex::{
50 self,
51 Exchange::{
52 Order as OrderV0, OrderV2, PerpetualInfo, PerpetualInfoV2, PositionInfo,
53 PositionInfoV2, getExchangeInfoReturn,
54 },
55 },
56 error::{DexError, ProviderError},
57 num, types,
58};
59
60const DEFAULT_ORDERS_PER_BATCH: usize = 1000;
64
65const DEFAULT_POSITIONS_PER_BATCH: usize = 1000;
69
70const PERPETUAL_PROBES_PER_BATCH: usize = 256;
74
75pub struct SnapshotBuilder<P> {
78 chain: Chain,
79 instance: dex::Exchange::ExchangeInstance<P>,
80 provider: P,
81 block_id: BlockId,
82 perpetuals: Vec<types::PerpetualId>,
83 accounts: Vec<types::AccountAddressOrID>,
84 all_positions: bool,
85 orders_per_batch: usize,
86 positions_per_batch: usize,
87}
88
89impl<P: Provider + Clone> SnapshotBuilder<P> {
90 pub fn new(chain: &Chain, provider: P) -> Self {
93 Self {
94 chain: chain.clone(),
95 instance: dex::Exchange::new(chain.exchange(), provider.clone()),
96 provider,
97 block_id: BlockId::Number(alloy::eips::BlockNumberOrTag::Safe),
98 perpetuals: chain.perpetuals.clone(),
99 accounts: vec![],
100 all_positions: false,
101 orders_per_batch: DEFAULT_ORDERS_PER_BATCH,
102 positions_per_batch: DEFAULT_POSITIONS_PER_BATCH,
103 }
104 }
105
106 pub fn at_block(mut self, block: BlockId) -> Self {
111 self.block_id = block;
112 self
113 }
114
115 pub fn with_perpetuals(mut self, perpetuals: Vec<types::PerpetualId>) -> Self {
120 self.perpetuals = perpetuals;
121 self
122 }
123
124 pub fn with_accounts(mut self, accounts: Vec<types::AccountAddressOrID>) -> Self {
127 self.accounts = accounts;
128 self.all_positions = false;
129 self
130 }
131
132 pub fn with_all_positions(mut self) -> Self {
136 self.accounts = vec![];
137 self.all_positions = true;
138 self
139 }
140
141 pub fn with_orders_per_batch(mut self, orders_per_batch: usize) -> Self {
145 self.orders_per_batch = orders_per_batch;
146 self
147 }
148
149 pub fn with_positions_per_batch(mut self, positions_per_batch: usize) -> Self {
152 self.positions_per_batch = positions_per_batch;
153 self
154 }
155
156 pub async fn build(mut self) -> Result<Exchange, DexError> {
158 let instant = self.normalize_block().await?;
160
161 let mut features = ContractFeatures::probe(
164 &self.instance,
165 self.block_id,
166 self.perpetuals.first().copied(),
167 )
168 .await;
169
170 if self.perpetuals.is_empty() {
173 self.perpetuals = discover_perpetuals(
174 &self.instance,
175 &self.provider,
176 self.block_id,
177 features,
178 self.chain.excluded_perpetuals(),
179 )
180 .await?;
181 if let Some(perp_id) = self.perpetuals.first().copied() {
184 features
185 .probe_v2_state_getters(&self.instance, self.block_id, perp_id)
186 .await;
187 }
188 }
189
190 let (
192 exchange_info,
193 funding_interval,
194 min_post,
195 min_settle,
196 recycle_fee,
197 is_halted,
198 num_of_accounts,
199 ) = self.exchange_info().await?;
200 let collateral_converter = num::Converter::new(exchange_info.collateralDecimals.to());
201
202 let (fee_schedules, perpetuals) =
207 futures::try_join!(self.fee_schedules(features), self.perpetuals(instant, features))?;
208
209 let accounts = if !self.accounts.is_empty() {
210 self.accounts(instant, &perpetuals, collateral_converter, features)
212 .await?
213 } else if self.all_positions {
214 self.position_accounts(
217 instant,
218 &perpetuals,
219 num_of_accounts.to(),
220 collateral_converter,
221 features,
222 )
223 .await?
224 } else {
225 HashMap::new()
226 };
227
228 Ok(Exchange::new(
229 self.chain.clone(),
230 instant,
231 features,
232 collateral_converter,
233 funding_interval.to(),
234 collateral_converter.from_unsigned(min_post),
235 collateral_converter.from_unsigned(min_settle),
236 collateral_converter.from_unsigned(recycle_fee),
237 fee_schedules,
238 perpetuals,
239 accounts,
240 is_halted,
241 self.all_positions,
242 ))
243 }
244
245 async fn fee_schedules(
258 &self,
259 features: ContractFeatures,
260 ) -> Result<FeeScheduleRegistry, DexError> {
261 if !features.keyed_fee_schedules() {
262 return Ok(FeeScheduleRegistry::new(
263 FeeSchedule::flat(FeeScheduleKey::Default, UD64::ZERO, UD64::ZERO),
264 FeeSchedule::flat(FeeScheduleKey::RwaDefault, UD64::ZERO, UD64::ZERO),
265 HashMap::new(),
266 ));
267 }
268 let fee_converter = num::fee_converter();
269 let (default_call, rwa_call) = (
270 self.instance
271 .getDefaultPerpFeeSchedule()
272 .block(self.block_id),
273 self.instance
274 .getFeeScheduleById(FeeScheduleKey::RwaDefault.to_raw())
275 .block(self.block_id),
276 );
277 let custom_calls = self.perpetuals.iter().map(|perp_id| {
278 let key = FeeScheduleKey::Custom(*perp_id);
279 let call = self
280 .instance
281 .getFeeScheduleById(key.to_raw())
282 .block(self.block_id);
283 async move {
284 call.call().await.map(|schedule| {
285 (
286 *perp_id,
287 FeeSchedule::new(
288 key,
289 schedule.takerFeesPer100K,
290 schedule.makerFeesPer100K,
291 fee_converter,
292 ),
293 )
294 })
295 }
296 });
297 let (default, rwa, custom) = futures::try_join!(
298 default_call.call().into_future(),
299 rwa_call.call().into_future(),
300 futures::future::try_join_all(custom_calls),
301 )
302 .map_err(|err| DexError::Provider(err.into()))?;
303 Ok(FeeScheduleRegistry::new(
304 FeeSchedule::new(
305 FeeScheduleKey::Default,
306 default.takerFeesPer100K,
307 default.makerFeesPer100K,
308 fee_converter,
309 ),
310 FeeSchedule::new(
311 FeeScheduleKey::RwaDefault,
312 rwa.takerFeesPer100K,
313 rwa.makerFeesPer100K,
314 fee_converter,
315 ),
316 custom.into_iter().collect(),
317 ))
318 }
319
320 async fn fetch_fee_schedule(
326 &self,
327 perp_id: U256,
328 features: ContractFeatures,
329 ) -> Result<FeeSchedule, alloy::contract::Error> {
330 let fee_converter = num::fee_converter();
331 if features.keyed_fee_schedules() {
332 self.instance
333 .getPerpFeeSchedule(perp_id)
334 .block(self.block_id)
335 .call()
336 .await
337 .map(|schedule| {
338 FeeSchedule::new(
339 FeeScheduleKey::from_raw(schedule.feeSchedId),
340 schedule.takerFeesPer100K,
341 schedule.makerFeesPer100K,
342 fee_converter,
343 )
344 })
345 } else {
346 let (maker_fee_call, taker_fee_call) = (
347 self.instance.getMakerFee(perp_id).block(self.block_id),
348 self.instance.getTakerFee(perp_id).block(self.block_id),
349 );
350 let (maker_fee, taker_fee) = futures::try_join!(
351 maker_fee_call.call().into_future(),
352 taker_fee_call.call().into_future(),
353 )?;
354 Ok(FeeSchedule::flat(
355 FeeScheduleKey::Default,
356 fee_converter.from_unsigned(taker_fee),
357 fee_converter.from_unsigned(maker_fee),
358 ))
359 }
360 }
361
362 async fn fetch_perpetual_info(
366 &self,
367 perp_id: U256,
368 features: ContractFeatures,
369 ) -> Result<PerpetualInfoV2, alloy::contract::Error> {
370 if features.v2_state_getters() {
371 self.instance
372 .getPerpetualInfoV2(perp_id)
373 .block(self.block_id)
374 .call()
375 .await
376 } else {
377 self.instance
378 .getPerpetualInfo(perp_id)
379 .block(self.block_id)
380 .call()
381 .await
382 .map(perpetual_info_v0_to_v2)
383 }
384 }
385
386 async fn fetch_position_info(
390 &self,
391 perp_id: U256,
392 account_id: U256,
393 features: ContractFeatures,
394 ) -> Result<PositionInfoV2, alloy::contract::Error> {
395 if features.v2_state_getters() {
396 self.instance
397 .getPositionV2(perp_id, account_id)
398 .block(self.block_id)
399 .call()
400 .await
401 .map(|r| r.positionInfo)
402 } else {
403 self.instance
404 .getPosition(perp_id, account_id)
405 .block(self.block_id)
406 .call()
407 .await
408 .map(|r| position_info_v0_to_v2(r.positionInfo))
409 }
410 }
411
412 async fn normalize_block(&mut self) -> Result<types::StateInstant, DexError> {
413 let block_header = self
416 .provider
417 .get_block(self.block_id)
418 .await
419 .map_err(|err| DexError::Provider(err.into()))?
420 .map(|b| b.into_header())
421 .ok_or(DexError::Provider(ProviderError::InvalidRequest(
422 "block not found".to_string(),
423 )))?;
424 self.block_id = BlockId::number(block_header.number);
425 Ok(types::StateInstant::new(block_header.number, block_header.timestamp))
426 }
427
428 async fn exchange_info(
429 &self,
430 ) -> Result<(getExchangeInfoReturn, U256, U256, U256, U256, bool, U256), DexError> {
431 let (
432 exchange_info_call,
433 funding_interval_call,
434 min_post_call,
435 min_settle_call,
436 recycle_fee_call,
437 is_halted_call,
438 num_of_accounts_call,
439 ) = (
440 self.instance.getExchangeInfo().block(self.block_id),
441 self.instance.getFundingInterval().block(self.block_id),
442 self.instance.getMinimumPostCNS().block(self.block_id),
443 self.instance.getMinimumSettleCNS().block(self.block_id),
444 self.instance.getRecycleFeeCNS().block(self.block_id),
445 self.instance.isHalted().block(self.block_id),
446 self.instance.numberOfAccounts().block(self.block_id),
450 );
451 futures::try_join!(
452 exchange_info_call.call().into_future(),
453 funding_interval_call.call().into_future(),
454 min_post_call.call().into_future(),
455 min_settle_call.call().into_future(),
456 recycle_fee_call.call().into_future(),
457 is_halted_call.call().into_future(),
458 num_of_accounts_call.call().into_future(),
459 )
460 .map_err(|err| DexError::Provider(err.into()))
461 }
462
463 async fn perpetuals(
464 &self,
465 instant: types::StateInstant,
466 features: ContractFeatures,
467 ) -> Result<HashMap<types::PerpetualId, perpetual::Perpetual>, DexError> {
468 let perpetual_futs = self.perpetuals.iter().map(|perp_id| async move {
469 let pid = U256::from(*perp_id);
470 let margins_call = self
471 .instance
472 .getMarginFractions(pid, U256::ZERO)
473 .block(self.block_id);
474
475 futures::try_join!(
476 self.fetch_perpetual_info(pid, features),
477 self.fetch_fee_schedule(pid, features),
478 margins_call.call().into_future(),
479 )
480 .map(|(perp_info, fee_schedule, margins)| (*perp_id, perp_info, fee_schedule, margins))
481 });
482
483 let mut perpetuals = futures::future::try_join_all(perpetual_futs)
484 .await
485 .map_err(|err| DexError::Provider(err.into()))?
486 .into_iter()
487 .map(|(perp_id, perp_info, fee_schedule, margins)| {
488 let perp = Perpetual::new(
489 instant,
490 perp_id,
491 &perp_info,
492 fee_schedule,
493 margins.perpInitMarginFracHdths,
494 margins.perpMaintMarginFracHdths,
495 );
496 (perp_id, perp)
497 })
498 .collect::<HashMap<_, _>>();
499
500 for perp in perpetuals.values_mut() {
502 self.perpetual_orders(perp, features).await?;
503 }
504
505 Ok(perpetuals)
506 }
507
508 async fn perpetual_orders(
509 &self,
510 perp: &mut perpetual::Perpetual,
511 features: ContractFeatures,
512 ) -> Result<(), DexError> {
513 let pid = U256::from(perp.id());
514 let order_id_index = self
515 .instance
516 .getOrderIdIndex(pid)
517 .block(self.block_id)
518 .call()
519 .await
520 .map_err(|err| DexError::Provider(err.into()))?;
521
522 let order_ids = order_id_index
523 .leaves
524 .into_iter()
525 .enumerate()
526 .flat_map(|(leaf, bitmap)| {
527 ((if leaf == 0 { 1 } else { 0 })..U256::BITS)
530 .filter(move |bit| bitmap.bit(*bit))
531 .map(move |bit| {
532 let id = (leaf * U256::BITS + bit) as u16;
533 std::num::NonZeroU16::new(id).expect("order id from bitmap cannot be 0")
535 })
536 })
537 .collect::<Vec<_>>();
538
539 let orders = self.fetch_orders(pid, &order_ids, features).await?;
540
541 let (instant, base_price, price_converter, size_converter, leverage_converter) = (
542 perp.instant(),
543 perp.base_price(),
544 perp.price_converter(),
545 perp.size_converter(),
546 perp.leverage_converter(),
547 );
548
549 let orders: Vec<Order> = orders
552 .into_iter()
553 .map(|ord| {
554 Order::from_snapshot(
555 instant,
556 ord,
557 base_price,
558 price_converter,
559 size_converter,
560 leverage_converter,
561 )
562 })
563 .collect::<Result<Vec<_>, _>>()
564 .map_err(|err| DexError::OrderParse(perp.id(), err))?;
565
566 perp.add_orders_from_snapshot(orders)
567 }
568
569 async fn fetch_orders(
574 &self,
575 perp_id: U256,
576 order_ids: &[types::OrderId],
577 features: ContractFeatures,
578 ) -> Result<Vec<OrderV2>, DexError> {
579 let order_ids = order_ids.to_vec();
580 if features.builder_attribution() {
581 aggregate_batched(order_ids, self.orders_per_batch, |chunk| {
582 let multicall = self
583 .provider
584 .multicall()
585 .block(self.block_id)
586 .dynamic()
587 .extend(
588 chunk
589 .iter()
590 .map(|oid| self.instance.getOrderV2(perp_id, U256::from(oid.get()))),
591 );
592 async move { multicall.aggregate().await }
593 })
594 .await
595 } else {
596 Ok(aggregate_batched(order_ids, self.orders_per_batch, |chunk| {
597 let multicall = self
598 .provider
599 .multicall()
600 .block(self.block_id)
601 .dynamic()
602 .extend(
603 chunk
604 .iter()
605 .map(|oid| self.instance.getOrder(perp_id, U256::from(oid.get()))),
606 );
607 async move { multicall.aggregate().await }
608 })
609 .await?
610 .into_iter()
611 .map(order_v0_to_v2)
612 .collect())
613 }
614 }
615
616 async fn accounts(
617 &self,
618 instant: types::StateInstant,
619 perpetuals: &HashMap<types::PerpetualId, perpetual::Perpetual>,
620 collateral_converter: num::Converter,
621 features: ContractFeatures,
622 ) -> Result<HashMap<types::AccountId, Account>, DexError> {
623 let account_futs = self.accounts.iter().map(|acc| async move {
624 let acc_info = match acc {
625 types::AccountAddressOrID::Address(addr) => self
626 .instance
627 .getAccountByAddr(*addr)
628 .block(self.block_id)
629 .call()
630 .await
631 .map_err(|err| DexError::Provider(err.into()))?,
632 types::AccountAddressOrID::ID(id) => self
633 .instance
634 .getAccountById(U256::from(*id))
635 .block(self.block_id)
636 .call()
637 .await
638 .map_err(|err| DexError::Provider(err.into()))?,
639 };
640 let fee_tier = self
641 .fetch_account_fee_tier(acc_info.accountId, features)
642 .await?;
643 let perps_with_positions = perpetuals_with_position(&acc_info.positions);
644 let position_futs = perps_with_positions.iter().map(|perp_id| async {
645 self.fetch_position_info(U256::from(*perp_id), acc_info.accountId, features)
646 .await
647 .map(|pos_info| (*perp_id, pos_info))
648 .map_err(|err| DexError::Provider(err.into()))
649 });
650 let positions = futures::future::try_join_all(position_futs).await?;
651 Ok::<_, DexError>((acc_info.accountId, acc_info, fee_tier, positions))
652 });
653
654 Ok(futures::future::try_join_all(account_futs)
655 .await?
656 .into_iter()
657 .map(|(acc_id, acc_info, fee_tier, positions)| {
658 (
659 acc_id.to(),
660 Account::new(
661 instant,
662 acc_id.to(),
663 &acc_info,
664 fee_tier,
665 positions
666 .into_iter()
667 .filter_map(|(perp_id, pos_info)| {
668 perpetuals.get(&perp_id).map(|perp| {
669 (
670 perp_id,
671 Position::new(
672 instant,
673 perp_id,
674 &pos_info,
675 collateral_converter,
676 perp.price_converter(),
677 perp.size_converter(),
678 perp.maintenance_margin(),
679 ),
680 )
681 })
682 })
683 .collect(),
684 collateral_converter,
685 ),
686 )
687 })
688 .collect())
689 }
690
691 async fn fetch_account_fee_tier(
694 &self,
695 account_id: U256,
696 features: ContractFeatures,
697 ) -> Result<Option<types::FeeTier>, DexError> {
698 if !features.keyed_fee_schedules() {
699 return Ok(None);
700 }
701 self.instance
702 .getAccountFeeTier(account_id)
703 .block(self.block_id)
704 .call()
705 .await
706 .map(|tier| Some(tier.to()))
707 .map_err(|err| DexError::Provider(err.into()))
708 }
709
710 async fn position_accounts(
711 &self,
712 instant: types::StateInstant,
713 perpetuals: &HashMap<types::PerpetualId, perpetual::Perpetual>,
714 num_accounts: usize,
715 collateral_converter: num::Converter,
716 features: ContractFeatures,
717 ) -> Result<HashMap<types::AccountId, Account>, DexError> {
718 let mut accounts: HashMap<types::AccountId, Account> = HashMap::new();
719 for (perp_id, perp) in perpetuals {
720 let pid = U256::from(*perp_id);
721 let infos = self
722 .fetch_position_infos_for_perp(pid, num_accounts, features)
723 .await?;
724 for info in infos {
725 if info.lotLNS.is_zero() {
726 continue;
727 }
728 let position = Position::new(
729 instant,
730 *perp_id,
731 &info,
732 collateral_converter,
733 perp.price_converter(),
734 perp.size_converter(),
735 perp.maintenance_margin(),
736 );
737 match accounts.entry(info.accountId.to()) {
738 hash_map::Entry::Occupied(mut e) => {
739 e.get_mut().positions_mut().insert(*perp_id, position);
740 },
741 hash_map::Entry::Vacant(e) => {
742 e.insert(Account::from_position(instant, position));
743 },
744 }
745 }
746 }
747
748 Ok(accounts)
749 }
750
751 async fn fetch_position_infos_for_perp(
754 &self,
755 perp_id: U256,
756 num_accounts: usize,
757 features: ContractFeatures,
758 ) -> Result<Vec<PositionInfoV2>, DexError> {
759 let account_ids = (1..num_accounts + 1).collect::<Vec<_>>();
760 if features.v2_state_getters() {
761 Ok(aggregate_batched(account_ids, self.positions_per_batch, |chunk| {
762 let multicall = self
763 .provider
764 .multicall()
765 .block(self.block_id)
766 .dynamic()
767 .extend(
768 chunk
769 .iter()
770 .map(|aid| self.instance.getPositionV2(perp_id, U256::from(*aid))),
771 );
772 async move { multicall.aggregate().await }
773 })
774 .await?
775 .into_iter()
776 .map(|r| r.positionInfo)
777 .collect())
778 } else {
779 Ok(aggregate_batched(account_ids, self.positions_per_batch, |chunk| {
780 let multicall = self
781 .provider
782 .multicall()
783 .block(self.block_id)
784 .dynamic()
785 .extend(
786 chunk
787 .iter()
788 .map(|aid| self.instance.getPosition(perp_id, U256::from(*aid))),
789 );
790 async move { multicall.aggregate().await }
791 })
792 .await?
793 .into_iter()
794 .map(|r| position_info_v0_to_v2(r.positionInfo))
795 .collect())
796 }
797 }
798}
799
800async fn aggregate_batched<T, R, F, Fut>(
817 items: Vec<T>,
818 batch_size: usize,
819 call: F,
820) -> Result<Vec<R>, DexError>
821where
822 T: Clone,
823 F: Fn(Vec<T>) -> Fut,
824 Fut: Future<Output = Result<Vec<R>, alloy::providers::MulticallError>>,
825{
826 let mut pending = items
829 .chunks(batch_size.max(1))
830 .enumerate()
831 .map(|(i, chunk)| (i * batch_size, chunk.to_vec()))
832 .collect::<Vec<_>>();
833 let mut fetched: Vec<(usize, Vec<R>)> = Vec::with_capacity(pending.len());
834
835 while !pending.is_empty() {
836 let results =
837 futures::future::join_all(pending.iter().map(|(_, chunk)| call(chunk.clone()))).await;
838 let mut retry = Vec::new();
839 for ((offset, chunk), result) in pending.into_iter().zip(results) {
840 match result {
841 Ok(values) => fetched.push((offset, values)),
842 Err(_) if chunk.len() > 1 => {
843 let mid = chunk.len() / 2;
844 retry.push((offset + mid, chunk[mid..].to_vec()));
845 retry.push((offset, chunk[..mid].to_vec()));
846 },
847 Err(err) => return Err(DexError::Provider(err.into())),
848 }
849 }
850 pending = retry;
851 }
852
853 fetched.sort_by_key(|(offset, _)| *offset);
854 Ok(fetched.into_iter().flat_map(|(_, values)| values).collect())
855}
856
857pub async fn listed_perpetuals<P: Provider + Clone>(
863 chain: &Chain,
864 provider: P,
865 block_id: BlockId,
866) -> Result<Vec<types::PerpetualId>, DexError> {
867 let instance = dex::Exchange::new(chain.exchange(), provider.clone());
868 let features = ContractFeatures::probe(&instance, block_id, None).await;
869 discover_perpetuals(&instance, &provider, block_id, features, chain.excluded_perpetuals()).await
870}
871
872async fn discover_perpetuals<P: Provider + Clone>(
881 instance: &dex::Exchange::ExchangeInstance<P>,
882 provider: &P,
883 block_id: BlockId,
884 features: ContractFeatures,
885 excluded: &[types::PerpetualId],
886) -> Result<Vec<types::PerpetualId>, DexError> {
887 if features.perpetual_discovery() {
888 let bitmap = instance
889 .getPerpetualExistsBitmap()
890 .block(block_id)
891 .call()
892 .await
893 .map_err(|err| DexError::Provider(err.into()))?;
894 return Ok(bitmap
895 .into_iter()
896 .enumerate()
897 .flat_map(|(word, bits)| {
898 (0..U256::BITS).filter_map(move |bit| {
899 let perp_id = (word * U256::BITS + bit) as types::PerpetualId;
900 (bits.bit(bit) && perp_id <= types::MAX_PERPETUAL_ID).then_some(perp_id)
901 })
902 })
903 .filter(|perp_id| !excluded.contains(perp_id))
904 .collect());
905 }
906
907 let probe_batch_futs = (0..=types::MAX_PERPETUAL_ID)
908 .filter(|perp_id| !excluded.contains(perp_id))
909 .chunks(PERPETUAL_PROBES_PER_BATCH)
910 .into_iter()
911 .map(|chunk| {
912 let perp_ids = chunk.collect::<Vec<_>>();
913 let multicall = provider
914 .multicall()
915 .block(block_id)
916 .dynamic::<dex::Exchange::getMarginFractionsCall>()
917 .extend_calls(perp_ids.iter().map(|perp_id| {
920 CallItem::from(instance.getMarginFractions(U256::from(*perp_id), U256::ZERO))
921 .with_failure_allowed()
922 }));
923 async move { multicall.aggregate3().await.map(|res| (perp_ids, res)) }
924 })
925 .collect::<Vec<_>>();
926
927 Ok(futures::future::try_join_all(probe_batch_futs)
928 .await
929 .map_err(|err| DexError::Provider(err.into()))?
930 .into_iter()
931 .flat_map(|(perp_ids, results)| {
932 perp_ids
933 .into_iter()
934 .zip(results)
935 .filter_map(|(perp_id, result)| result.is_ok().then_some(perp_id))
936 })
937 .collect())
938}
939
940fn position_info_v0_to_v2(v0: PositionInfo) -> PositionInfoV2 {
941 PositionInfoV2 {
942 accountId: v0.accountId,
943 nextNodeId: v0.nextNodeId,
944 prevNodeId: v0.prevNodeId,
945 positionType: v0.positionType,
946 depositCNS: v0.depositCNS,
947 pricePNS: v0.pricePNS,
948 lotLNS: v0.lotLNS,
949 entryBlock: v0.entryBlock,
950 pnlCNS: v0.pnlCNS,
951 deltaPnlCNS: v0.deltaPnlCNS,
952 premiumPnlCNS: v0.premiumPnlCNS,
953 priceResiduePNSQ16: U256::ZERO,
954 }
955}
956
957fn order_v0_to_v2(v0: OrderV0) -> OrderV2 {
958 OrderV2 {
959 accountId: v0.accountId,
960 orderType: v0.orderType,
961 priceONS: v0.priceONS,
962 lotLNS: v0.lotLNS,
963 recycleFeeRaw: v0.recycleFeeRaw,
964 expiryBlock: v0.expiryBlock,
965 leverageHdths: v0.leverageHdths,
966 orderId: v0.orderId,
967 prevOrderId: v0.prevOrderId,
968 nextOrderId: v0.nextOrderId,
969 maxNegPnlCollatBPS: v0.maxNegPnlCollatBPS,
970 builderId: 0,
971 builderFeePer100K: 0,
972 }
973}
974
975fn perpetual_info_v0_to_v2(v0: PerpetualInfo) -> PerpetualInfoV2 {
976 PerpetualInfoV2 {
977 name: v0.name,
978 symbol: v0.symbol,
979 priceDecimals: v0.priceDecimals,
980 lotDecimals: v0.lotDecimals,
981 linkFeedId: v0.linkFeedId,
982 priceTolPer100K: v0.priceTolPer100K,
983 marginTol: v0.marginTol,
984 marginTolDecimals: v0.marginTolDecimals,
985 refPriceMaxAgeSec: v0.refPriceMaxAgeSec,
986 positionBalanceCNS: v0.positionBalanceCNS,
987 insuranceBalanceCNS: v0.insuranceBalanceCNS,
988 markPNS: v0.markPNS,
989 markTimestamp: v0.markTimestamp,
990 lastPNS: v0.lastPNS,
991 lastTimestamp: v0.lastTimestamp,
992 oraclePNS: v0.oraclePNS,
993 oracleTimestampSec: v0.oracleTimestampSec,
994 longOpenInterestLNS: v0.longOpenInterestLNS,
995 shortOpenInterestLNS: v0.shortOpenInterestLNS,
996 fundingStartBlock: v0.fundingStartBlock,
997 fundingRatePct100k: v0.fundingRatePct100k,
998 absFundingClampPctPer100K: v0.absFundingClampPctPer100K,
999 status: v0.status,
1000 basePricePNS: v0.basePricePNS,
1001 maxBidPriceONS: v0.maxBidPriceONS,
1002 minBidPriceONS: v0.minBidPriceONS,
1003 maxAskPriceONS: v0.maxAskPriceONS,
1004 minAskPriceONS: v0.minAskPriceONS,
1005 numOrders: v0.numOrders,
1006 ignOracle: v0.ignOracle,
1007 fundingSumScalingExp: U256::ZERO,
1008 }
1009}