1use std::{
12 any::Any,
13 collections::HashMap,
14 time::{SystemTime, UNIX_EPOCH},
15};
16
17use alloy::primitives::U256;
18use num_bigint::{BigUint, ToBigUint};
19use num_traits::Euclid;
20use thiserror::Error;
21use tracing::trace;
22use tycho_common::{
23 dto::ProtocolStateDelta,
24 models::token::Token,
25 simulation::{
26 errors::{SimulationError, TransitionError},
27 protocol_sim::{Balances, GetAmountOutResult, ProtocolSim},
28 },
29 Bytes,
30};
31
32use crate::evm::{
33 engine_db::{create_engine, SHARED_TYCHO_DB},
34 protocol::{
35 fluid::{v1::constant::RESERVES_RESOLVER, vm},
36 u256_num::{biguint_to_u256, u256_to_biguint, u256_to_f64},
37 utils::add_fee_markup,
38 },
39};
40
41mod constant {
42 use alloy::{hex, primitives::U256};
43
44 pub const MAX_PRICE_DIFF: U256 = U256::from_limbs([5, 0, 0, 0]); pub const MIN_SWAP_LIQUIDITY: U256 = U256::from_limbs([8500, 0, 0, 0]); pub const SIX_DECIMALS: U256 = U256::from_limbs([1000000, 0, 0, 0]); pub const TWO_DECIMALS: U256 = U256::from_limbs([100, 0, 0, 0]); pub const B_I1E18: U256 = U256::from_limbs([0x0DE0B6B3A7640000, 0, 0, 0]); pub const B_I1E27: U256 = U256::from_limbs([0x9fd0803ce8000000, 0x33b2e3c, 0, 0]); pub const DEX_AMOUNT_DECIMALS: i64 = 12;
51 pub const FEE_PERCENT_PRECISION: U256 = U256::from_limbs([10000, 0, 0, 0]);
52 pub const ZERO_ADDRESS: &[u8] = &hex!("0x0000000000000000000000000000000000000000");
53 pub const NATIVE_ADDRESS: &[u8] = &hex!("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE");
54 pub const RESERVES_RESOLVER: &[u8] = &hex!("0xc93876c0eed99645dd53937b25433e311881a27c");
55}
56
57#[derive(Debug, Clone, PartialEq)]
58pub struct FluidV1 {
59 pool_address: Bytes,
60 token0: Token,
61 token1: Token,
62 collateral_reserves: CollateralReserves,
63 debt_reserves: DebtReserves,
64 dex_limits: DexLimits,
65 center_price: U256,
66 fee: U256,
67 sync_time: u64,
68 pool_reserve0: U256,
69 pool_reserve1: U256,
70}
71
72#[derive(Debug, Clone, PartialEq)]
73pub(super) struct CollateralReserves {
74 pub(super) token0_real_reserves: U256,
75 pub(super) token1_real_reserves: U256,
76 pub(super) token0_imaginary_reserves: U256,
77 pub(super) token1_imaginary_reserves: U256,
78}
79
80#[derive(Debug, Clone, PartialEq)]
81pub(super) struct DebtReserves {
82 pub(super) token0_real_reserves: U256,
83 pub(super) token1_real_reserves: U256,
84 pub(super) token0_imaginary_reserves: U256,
85 pub(super) token1_imaginary_reserves: U256,
86}
87
88#[derive(Debug, Clone, PartialEq)]
89pub(super) struct DexLimits {
90 pub(super) borrowable_token0: TokenLimit,
91 pub(super) borrowable_token1: TokenLimit,
92 pub(super) withdrawable_token0: TokenLimit,
93 pub(super) withdrawable_token1: TokenLimit,
94}
95
96#[derive(Debug, Clone, PartialEq)]
97pub(super) struct TokenLimit {
98 pub(super) available: U256,
99 pub(super) expands_to: U256,
100 pub(super) expand_duration: U256,
101}
102
103#[derive(Debug, Error)]
104enum SwapError {
105 #[error("Insufficient reserve: tokenOut amount exceeds reserve")]
106 InsufficientReserve,
107 #[error("Insufficient reserve: tokenOut amount exceeds borrowable limit")]
108 InsufficientBorrowable,
109 #[error("Insufficient reserve: tokenOut amount exceeds withdrawable limit")]
110 InsufficientWithdrawable,
111 #[error("Insufficient reserve: tokenOut amount exceeds max price limit")]
112 InsufficientMaxPrice,
113 #[error("Invalid reserves ratio")]
114 VerifyReservesRatiosInvalid,
115 #[error("No pools are enabled")]
116 NoPoolsEnabled,
117 #[error("InvalidAmountIn: Amount too low")]
118 InvalidAmountIn,
119}
120
121impl From<SwapError> for SimulationError {
122 fn from(value: SwapError) -> Self {
123 Self::FatalError(value.to_string())
124 }
125}
126impl FluidV1 {
127 #[allow(clippy::too_many_arguments)]
128 pub(super) fn new(
129 pool_address: &Bytes,
130 token0: &Token,
131 token1: &Token,
132 collateral_reserves: CollateralReserves,
133 debt_reserves: DebtReserves,
134 dex_limits: DexLimits,
135 center_price: U256,
136 fee: U256,
137 sync_time: u64,
138 ) -> Self {
139 let pool_reserve0 = get_max_reserves(
140 token0.decimals as u8,
141 &dex_limits.withdrawable_token0,
142 &dex_limits.borrowable_token0,
143 &collateral_reserves.token0_real_reserves,
144 &debt_reserves.token0_real_reserves,
145 );
146 let pool_reserve1 = get_max_reserves(
147 token1.decimals as u8,
148 &dex_limits.withdrawable_token1,
149 &dex_limits.borrowable_token1,
150 &collateral_reserves.token1_real_reserves,
151 &debt_reserves.token1_real_reserves,
152 );
153
154 let (token0_normalized, token1_normalized) =
157 if FluidV1::normalize_native_address(&token0.address) <
158 FluidV1::normalize_native_address(&token1.address)
159 {
160 (token0.clone(), token1.clone())
161 } else {
162 (token1.clone(), token0.clone())
163 };
164 Self {
165 pool_address: pool_address.clone(),
166 token0: token0_normalized,
167 token1: token1_normalized,
168 collateral_reserves,
169 debt_reserves,
170 dex_limits,
171 center_price,
172 fee,
173 sync_time,
174 pool_reserve0,
175 pool_reserve1,
176 }
177 }
178
179 fn normalize_native_address(address: &Bytes) -> &[u8] {
180 if address == constant::ZERO_ADDRESS {
181 constant::NATIVE_ADDRESS
182 } else {
183 address
184 }
185 }
186}
187
188impl ProtocolSim for FluidV1 {
189 fn fee(&self) -> f64 {
190 let fee = u256_to_f64(self.fee).expect("Fluid fee values are safe to convert");
191 let precision =
192 u256_to_f64(constant::FEE_PERCENT_PRECISION).expect("FEE_PERCENT_PRECISION is safe");
193 fee / precision / 100.0
196 }
197
198 fn spot_price(&self, base: &Token, _quote: &Token) -> Result<f64, SimulationError> {
199 let price_f64 = if !self
200 .collateral_reserves
201 .token0_imaginary_reserves
202 .is_zero()
203 {
204 u256_to_f64(
205 self.collateral_reserves
206 .token1_imaginary_reserves,
207 )? / u256_to_f64(
208 self.collateral_reserves
209 .token0_imaginary_reserves,
210 )?
211 } else {
212 u256_to_f64(
213 self.debt_reserves
214 .token1_imaginary_reserves,
215 )? / u256_to_f64(
216 self.debt_reserves
217 .token0_imaginary_reserves,
218 )?
219 };
220 let oriented_price_f64 =
221 if base.address == self.token0.address { price_f64 } else { 1.0 / price_f64 };
222
223 Ok(add_fee_markup(oriented_price_f64, self.fee()))
224 }
225
226 fn get_amount_out(
227 &self,
228 amount_in: BigUint,
229 token_in: &Token,
230 token_out: &Token,
231 ) -> Result<GetAmountOutResult, SimulationError> {
232 if amount_in == BigUint::from(0u32) {
233 return Ok(GetAmountOutResult {
234 amount: BigUint::from(0u32),
235 gas: BigUint::from(155433u32),
236 new_state: Box::new(self.clone()),
237 });
238 }
239 let zero2one = self.token0.address == token_in.address;
240
241 let (token_in_decimals, token_out_decimals) = (token_in.decimals, token_out.decimals);
242
243 let amount_in = biguint_to_u256(&amount_in);
244 let fee = amount_in * self.fee / constant::SIX_DECIMALS;
245
246 let amount_in_after_fee = amount_in - fee;
247 let amount_in_adjusted = to_adjusted_amount(amount_in_after_fee, token_in_decimals as i64);
248
249 if amount_in_adjusted < constant::SIX_DECIMALS ||
250 amount_in_after_fee < constant::TWO_DECIMALS
251 {
252 return Err(SwapError::InvalidAmountIn.into())
253 }
254 let mut new_col_reserves = self.collateral_reserves.clone();
255 let mut new_debt_reserves = self.debt_reserves.clone();
256 let mut new_limits = self.dex_limits.clone();
257
258 let amount_out = swap_in_adjusted(
259 zero2one,
260 amount_in_adjusted,
261 &mut new_col_reserves,
262 &mut new_debt_reserves,
263 token_out_decimals as i64,
264 &mut new_limits,
265 self.center_price,
266 self.sync_time,
267 )?;
268
269 let reserve = if zero2one { self.pool_reserve1 } else { self.pool_reserve0 };
270 if amount_out > reserve {
271 return Err(SwapError::InsufficientReserve.into())
272 }
273
274 let result = GetAmountOutResult::new(
275 u256_to_biguint(amount_out),
276 155433.to_biguint().expect("infallible"),
277 Box::new(Self {
278 pool_address: self.pool_address.clone(),
279 token0: self.token0.clone(),
280 token1: self.token1.clone(),
281 collateral_reserves: new_col_reserves,
282 debt_reserves: new_debt_reserves,
283 dex_limits: new_limits,
284 center_price: self.center_price,
285 fee: self.fee,
286 sync_time: self.sync_time,
287 pool_reserve0: self.pool_reserve0,
288 pool_reserve1: self.pool_reserve1,
289 }),
290 );
291 Ok(result)
292 }
293
294 fn get_limits(
295 &self,
296 sell_token: Bytes,
297 buy_token: Bytes,
298 ) -> Result<(BigUint, BigUint), SimulationError> {
299 let zero2one = sell_token == self.token0.address;
300
301 let (upper_bound_out, out_decimals, in_decimals) = if zero2one {
302 (
303 to_adjusted_amount(
304 self.dex_limits
305 .withdrawable_token0
306 .available +
307 self.dex_limits
308 .borrowable_token0
309 .available,
310 self.token0.decimals as i64,
311 ),
312 self.token1.decimals,
313 self.token0.decimals,
314 )
315 } else {
316 (
317 to_adjusted_amount(
318 self.dex_limits
319 .withdrawable_token1
320 .available +
321 self.dex_limits
322 .borrowable_token1
323 .available,
324 self.token1.decimals as i64,
325 ),
326 self.token0.decimals,
327 self.token1.decimals,
328 )
329 };
330 if upper_bound_out == U256::ZERO {
331 trace!("Upper bound is zero for {}", self.pool_address);
332 return Ok((BigUint::ZERO, BigUint::ZERO));
333 }
334 let delta = U256::from(10).pow(U256::from(2));
335 let (max_valid, res) = find_max_valid_u256(upper_bound_out, delta, |amount| {
336 let mut col_clone = self.collateral_reserves.clone();
337 let mut debt_clone = self.debt_reserves.clone();
338 let mut limits_clone = self.dex_limits.clone();
339 swap_in_adjusted(
340 zero2one,
341 amount,
342 &mut col_clone,
343 &mut debt_clone,
344 out_decimals as i64,
345 &mut limits_clone,
346 self.center_price,
347 self.sync_time,
348 )
349 });
350 Ok((
351 u256_to_biguint(from_adjusted_amount(max_valid, in_decimals as i64)),
352 u256_to_biguint(res.unwrap_or_else(|| {
353 trace!(
354 "All evaluations errored during limit search for {} -> {}",
355 sell_token,
356 buy_token
357 );
358 U256::ZERO
359 })),
360 ))
361 }
362
363 fn delta_transition(
364 &mut self,
365 _delta: ProtocolStateDelta,
366 _tokens: &HashMap<Bytes, Token>,
367 _balances: &Balances,
368 ) -> Result<(), TransitionError<String>> {
369 let engine = create_engine(SHARED_TYCHO_DB.clone(), false).expect("Infallible");
370
371 let state = vm::decode_from_vm(
372 &self.pool_address,
373 &self.token0,
374 &self.token1,
375 RESERVES_RESOLVER,
376 engine,
377 )?;
378
379 trace!(?state, "Calling delta transition for {}", &self.pool_address);
380
381 self.collateral_reserves = state.collateral_reserves;
382 self.debt_reserves = state.debt_reserves;
383 self.dex_limits = state.dex_limits;
384 self.center_price = state.center_price;
385 self.fee = state.fee;
386 self.sync_time = state.sync_time;
387
388 self.pool_reserve0 = get_max_reserves(
389 self.token0.decimals as u8,
390 &self.dex_limits.withdrawable_token0,
391 &self.dex_limits.borrowable_token0,
392 &self
393 .collateral_reserves
394 .token0_real_reserves,
395 &self.debt_reserves.token0_real_reserves,
396 );
397 self.pool_reserve1 = get_max_reserves(
398 self.token1.decimals as u8,
399 &self.dex_limits.withdrawable_token1,
400 &self.dex_limits.borrowable_token1,
401 &self
402 .collateral_reserves
403 .token1_real_reserves,
404 &self.debt_reserves.token1_real_reserves,
405 );
406 Ok(())
407 }
408
409 fn clone_box(&self) -> Box<dyn ProtocolSim> {
410 Box::new(self.clone())
411 }
412
413 fn as_any(&self) -> &dyn Any {
414 self
415 }
416
417 fn as_any_mut(&mut self) -> &mut dyn Any {
418 self
419 }
420
421 fn eq(&self, other: &dyn ProtocolSim) -> bool {
422 if let Some(other_state) = other.as_any().downcast_ref::<Self>() {
423 self == other_state
424 } else {
425 false
426 }
427 }
428}
429
430pub fn find_max_valid_u256<T, E, F>(upper_bound: U256, delta: U256, mut f: F) -> (U256, Option<T>)
440where
441 F: FnMut(U256) -> Result<T, E>,
442 E: std::fmt::Debug,
443{
444 let mut low = U256::ZERO;
445 let mut high = upper_bound;
446 let mut best = U256::ZERO;
447 let mut best_result: Option<T> = None;
448
449 while high > low + delta {
450 let mid = (low + high) / U256::from(2);
451
452 match f(mid) {
453 Ok(result) => {
454 best = mid;
455 best_result = Some(result);
456 low = mid;
457 }
458 Err(_) => {
459 high = mid;
460 }
461 }
462 }
463
464 (best, best_result)
465}
466
467#[allow(clippy::too_many_arguments)]
468fn swap_in_adjusted(
469 swap0_to_1: bool,
470 amount_to_swap: U256,
471 col_reserves: &mut CollateralReserves,
472 debt_reserves: &mut DebtReserves,
473 out_decimals: i64,
474 current_limits: &mut DexLimits,
475 center_price: U256,
476 sync_time: u64,
477) -> Result<U256, SwapError> {
478 let (
479 col_reserve_in,
480 col_reserve_out,
481 col_i_reserve_in,
482 col_i_reserve_out,
483 debt_reserve_in,
484 debt_reserve_out,
485 debt_i_reserve_in,
486 debt_i_reserve_out,
487 borrowable,
488 withdrawable,
489 ) = if swap0_to_1 {
490 (
491 col_reserves.token0_real_reserves,
492 col_reserves.token1_real_reserves,
493 col_reserves.token0_imaginary_reserves,
494 col_reserves.token1_imaginary_reserves,
495 debt_reserves.token0_real_reserves,
496 debt_reserves.token1_real_reserves,
497 debt_reserves.token0_imaginary_reserves,
498 debt_reserves.token1_imaginary_reserves,
499 get_expanded_limit(sync_time, ¤t_limits.borrowable_token1),
500 get_expanded_limit(sync_time, ¤t_limits.withdrawable_token1),
501 )
502 } else {
503 (
504 col_reserves.token1_real_reserves,
505 col_reserves.token0_real_reserves,
506 col_reserves.token1_imaginary_reserves,
507 col_reserves.token0_imaginary_reserves,
508 debt_reserves.token1_real_reserves,
509 debt_reserves.token0_real_reserves,
510 debt_reserves.token1_imaginary_reserves,
511 debt_reserves.token0_imaginary_reserves,
512 get_expanded_limit(sync_time, ¤t_limits.borrowable_token0),
513 get_expanded_limit(sync_time, ¤t_limits.withdrawable_token0),
514 )
515 };
516
517 let borrowable = to_adjusted_amount(borrowable, out_decimals);
519 let withdrawable = to_adjusted_amount(withdrawable, out_decimals);
520
521 let col_pool_enabled = col_reserves.token0_real_reserves > U256::ZERO &&
523 col_reserves.token1_real_reserves > U256::ZERO &&
524 col_reserves.token0_imaginary_reserves > U256::ZERO &&
525 col_reserves.token1_imaginary_reserves > U256::ZERO;
526
527 let debt_pool_enabled = debt_reserves.token0_real_reserves > U256::ZERO &&
528 debt_reserves.token1_real_reserves > U256::ZERO &&
529 debt_reserves.token0_imaginary_reserves > U256::ZERO &&
530 debt_reserves.token1_imaginary_reserves > U256::ZERO;
531
532 if !col_pool_enabled && !debt_pool_enabled {
533 return Err(SwapError::NoPoolsEnabled);
534 }
535
536 let a = if col_pool_enabled && debt_pool_enabled {
537 swap_routing_in(
538 amount_to_swap,
539 col_i_reserve_out,
540 col_i_reserve_in,
541 debt_i_reserve_out,
542 debt_i_reserve_in,
543 )
544 } else if debt_pool_enabled {
545 U256::MAX } else if col_pool_enabled {
547 amount_to_swap + U256::ONE } else {
549 return Err(SwapError::NoPoolsEnabled);
550 };
551
552 let (amount_in_collateral, amount_out_collateral, amount_in_debt, amount_out_debt) = if a ==
553 U256::ZERO ||
554 a == U256::MAX
555 {
556 let amount_out_debt = get_amount_out(amount_to_swap, debt_i_reserve_in, debt_i_reserve_out);
558 (U256::ZERO, U256::ZERO, amount_to_swap, amount_out_debt)
559 } else if a >= amount_to_swap {
560 let amount_out_collateral =
562 get_amount_out(amount_to_swap, col_i_reserve_in, col_i_reserve_out);
563 (amount_to_swap, amount_out_collateral, U256::ZERO, U256::ZERO)
564 } else {
565 let amount_in_debt = amount_to_swap - a;
567 let amount_out_debt = get_amount_out(amount_in_debt, debt_i_reserve_in, debt_i_reserve_out);
568 let amount_out_collateral = get_amount_out(a, col_i_reserve_in, col_i_reserve_out);
569 (a, amount_out_collateral, amount_in_debt, amount_out_debt)
570 };
571
572 if amount_out_debt > debt_reserve_out {
573 return Err(SwapError::InsufficientReserve);
574 }
575
576 if amount_out_collateral > col_reserve_out {
577 return Err(SwapError::InsufficientReserve);
578 }
579
580 if amount_out_debt > borrowable {
581 return Err(SwapError::InsufficientBorrowable);
582 }
583
584 if amount_out_collateral > withdrawable {
585 return Err(SwapError::InsufficientWithdrawable);
586 }
587
588 if amount_in_collateral > U256::ZERO {
589 let reserves_ratio_valid = if swap0_to_1 {
590 verify_token1_reserves(
591 col_reserve_in + amount_in_collateral,
592 col_reserve_out - amount_out_collateral,
593 center_price,
594 )
595 } else {
596 verify_token0_reserves(
597 col_reserve_out - amount_out_collateral,
598 col_reserve_in + amount_in_collateral,
599 center_price,
600 )
601 };
602 if !reserves_ratio_valid {
603 return Err(SwapError::VerifyReservesRatiosInvalid);
604 }
605 }
606
607 if amount_in_debt > U256::ZERO {
608 let reserves_ratio_valid = if swap0_to_1 {
609 verify_token1_reserves(
610 debt_reserve_in + amount_in_debt,
611 debt_reserve_out - amount_out_debt,
612 center_price,
613 )
614 } else {
615 verify_token0_reserves(
616 debt_reserve_out - amount_out_debt,
617 debt_reserve_in + amount_in_debt,
618 center_price,
619 )
620 };
621 if !reserves_ratio_valid {
622 return Err(SwapError::VerifyReservesRatiosInvalid);
623 }
624 }
625
626 let (old_price, new_price) = if amount_in_collateral > amount_in_debt {
627 if swap0_to_1 {
628 (
629 col_i_reserve_out * constant::B_I1E27 / col_i_reserve_in,
630 (col_i_reserve_out - amount_out_collateral) * constant::B_I1E27 /
631 (col_i_reserve_in + amount_in_collateral),
632 )
633 } else {
634 (
635 col_i_reserve_in * constant::B_I1E27 / col_i_reserve_out,
636 (col_i_reserve_in + amount_in_collateral) * constant::B_I1E27 /
637 (col_i_reserve_out - amount_out_collateral),
638 )
639 }
640 } else if swap0_to_1 {
641 (
642 debt_i_reserve_out * constant::B_I1E27 / debt_i_reserve_in,
643 (debt_i_reserve_out - amount_out_debt) * constant::B_I1E27 /
644 (debt_i_reserve_in + amount_in_debt),
645 )
646 } else {
647 (
648 debt_i_reserve_in * constant::B_I1E27 / debt_i_reserve_out,
649 (debt_i_reserve_in + amount_in_debt) * constant::B_I1E27 /
650 (debt_i_reserve_out - amount_out_debt),
651 )
652 };
653
654 let price_diff = old_price.abs_diff(new_price);
655 let max_price_diff = old_price * constant::MAX_PRICE_DIFF / constant::TWO_DECIMALS;
656
657 if price_diff > max_price_diff {
658 return Err(SwapError::InsufficientMaxPrice);
659 }
660
661 if amount_in_collateral > U256::ZERO {
662 update_collateral_reserves_and_limits(
663 swap0_to_1,
664 amount_in_collateral,
665 amount_out_collateral,
666 col_reserves,
667 current_limits,
668 out_decimals,
669 );
670 }
671
672 if amount_in_debt > U256::ZERO {
673 update_debt_reserves_and_limits(
674 swap0_to_1,
675 amount_in_debt,
676 amount_out_debt,
677 debt_reserves,
678 current_limits,
679 out_decimals,
680 );
681 }
682
683 Ok(from_adjusted_amount(amount_out_collateral + amount_out_debt, out_decimals))
684}
685
686#[allow(clippy::too_many_arguments, dead_code)]
687fn swap_out_adjusted(
688 swap0_to_1: bool,
689 amount_to_receive: U256,
690 col_reserves: &mut CollateralReserves,
691 debt_reserves: &mut DebtReserves,
692 in_decimals: i64,
693 out_decimals: i64,
694 current_limits: &mut DexLimits,
695 center_price: U256,
696 sync_time: u64,
697) -> Result<U256, SwapError> {
698 let (
699 col_reserve_in,
700 col_reserve_out,
701 col_i_reserve_in,
702 col_i_reserve_out,
703 debt_reserve_in,
704 debt_reserve_out,
705 debt_i_reserve_in,
706 debt_i_reserve_out,
707 borrowable,
708 withdrawable,
709 ) = if swap0_to_1 {
710 (
711 col_reserves.token0_real_reserves,
712 col_reserves.token1_real_reserves,
713 col_reserves.token0_imaginary_reserves,
714 col_reserves.token1_imaginary_reserves,
715 debt_reserves.token0_real_reserves,
716 debt_reserves.token1_real_reserves,
717 debt_reserves.token0_imaginary_reserves,
718 debt_reserves.token1_imaginary_reserves,
719 get_expanded_limit(sync_time, ¤t_limits.borrowable_token1),
720 get_expanded_limit(sync_time, ¤t_limits.withdrawable_token1),
721 )
722 } else {
723 (
724 col_reserves.token1_real_reserves,
725 col_reserves.token0_real_reserves,
726 col_reserves.token1_imaginary_reserves,
727 col_reserves.token0_imaginary_reserves,
728 debt_reserves.token1_real_reserves,
729 debt_reserves.token0_real_reserves,
730 debt_reserves.token1_imaginary_reserves,
731 debt_reserves.token0_imaginary_reserves,
732 get_expanded_limit(sync_time, ¤t_limits.borrowable_token0),
733 get_expanded_limit(sync_time, ¤t_limits.withdrawable_token0),
734 )
735 };
736
737 let borrowable = to_adjusted_amount(borrowable, out_decimals);
738 let withdrawable = to_adjusted_amount(withdrawable, out_decimals);
739
740 let col_pool_enabled = col_reserves.token0_real_reserves > U256::ZERO &&
741 col_reserves.token1_real_reserves > U256::ZERO &&
742 col_reserves.token0_imaginary_reserves > U256::ZERO &&
743 col_reserves.token1_imaginary_reserves > U256::ZERO;
744
745 let debt_pool_enabled = debt_reserves.token0_real_reserves > U256::ZERO &&
746 debt_reserves.token1_real_reserves > U256::ZERO &&
747 debt_reserves.token0_imaginary_reserves > U256::ZERO &&
748 debt_reserves.token1_imaginary_reserves > U256::ZERO;
749
750 if !col_pool_enabled && !debt_pool_enabled {
751 return Err(SwapError::NoPoolsEnabled);
752 }
753
754 let a = if col_pool_enabled && debt_pool_enabled {
755 swap_routing_out(
756 amount_to_receive,
757 col_i_reserve_out,
758 col_i_reserve_in,
759 debt_i_reserve_out,
760 debt_i_reserve_in,
761 )
762 } else if debt_pool_enabled {
763 U256::MAX
764 } else if col_pool_enabled {
765 amount_to_receive + U256::ONE
766 } else {
767 return Err(SwapError::NoPoolsEnabled);
768 };
769
770 let mut trigger_update_debt_reserves = false;
771 let mut trigger_update_col_reserves = false;
772
773 let (amount_in_collateral, amount_out_collateral, amount_in_debt, amount_out_debt) =
774 if a == U256::ZERO || a == U256::MAX {
775 let amount_in_debt =
776 get_amount_in(amount_to_receive, debt_i_reserve_in, debt_i_reserve_out);
777 if amount_to_receive > debt_reserve_out {
778 return Err(SwapError::InsufficientReserve);
779 }
780
781 trigger_update_debt_reserves = true;
782 (U256::ZERO, U256::ZERO, amount_in_debt, amount_to_receive)
783 } else if a >= amount_to_receive {
784 let amount_in_collateral =
785 get_amount_in(amount_to_receive, col_i_reserve_in, col_i_reserve_out);
786
787 if amount_to_receive > col_reserve_out {
788 return Err(SwapError::InsufficientReserve);
789 }
790
791 trigger_update_col_reserves = true;
792 (amount_in_collateral, amount_to_receive, U256::ZERO, U256::ZERO)
793 } else {
794 let amount_out_collateral = a;
795 let amount_in_collateral =
796 get_amount_in(amount_out_collateral, col_i_reserve_in, col_i_reserve_out);
797 let amount_out_debt = amount_to_receive - amount_out_collateral;
798 let amount_in_debt =
799 get_amount_in(amount_out_debt, debt_i_reserve_in, debt_i_reserve_out);
800
801 if amount_out_debt > debt_reserve_out || amount_out_collateral > col_reserve_out {
802 return Err(SwapError::InsufficientReserve);
803 }
804
805 (amount_in_collateral, amount_out_collateral, amount_in_debt, amount_out_debt)
806 };
807
808 if amount_in_debt > borrowable {
809 return Err(SwapError::InsufficientBorrowable);
810 }
811
812 if amount_in_collateral > withdrawable {
813 return Err(SwapError::InsufficientWithdrawable);
814 }
815
816 if amount_in_collateral > U256::ZERO {
817 let reserves_ratio_valid = if swap0_to_1 {
818 verify_token1_reserves(
819 col_reserve_in + amount_in_collateral,
820 col_reserve_out - amount_out_collateral,
821 center_price,
822 )
823 } else {
824 verify_token0_reserves(
825 col_reserve_out - amount_out_collateral,
826 col_reserve_in + amount_in_collateral,
827 center_price,
828 )
829 };
830 if !reserves_ratio_valid {
831 return Err(SwapError::VerifyReservesRatiosInvalid);
832 }
833 }
834
835 if amount_in_debt > U256::ZERO {
836 let reserves_ratio_valid = if swap0_to_1 {
837 verify_token1_reserves(
838 debt_reserve_in + amount_in_debt,
839 debt_reserve_out - amount_out_debt,
840 center_price,
841 )
842 } else {
843 verify_token0_reserves(
844 debt_reserve_out - amount_out_debt,
845 debt_reserve_in + amount_in_debt,
846 center_price,
847 )
848 };
849 if !reserves_ratio_valid {
850 return Err(SwapError::VerifyReservesRatiosInvalid);
851 }
852 }
853
854 let (old_price, new_price) = if amount_in_collateral > amount_in_debt {
855 if swap0_to_1 {
856 (
857 col_i_reserve_out * constant::B_I1E27 / col_i_reserve_in,
858 (col_i_reserve_out - amount_out_collateral) * constant::B_I1E27 /
859 (col_i_reserve_in + amount_in_collateral),
860 )
861 } else {
862 (
863 col_i_reserve_in * constant::B_I1E27 / col_i_reserve_out,
864 (col_i_reserve_in + amount_in_collateral) * constant::B_I1E27 /
865 (col_i_reserve_out - amount_out_collateral),
866 )
867 }
868 } else if swap0_to_1 {
869 (
870 debt_i_reserve_out * constant::B_I1E27 / debt_i_reserve_in,
871 (debt_i_reserve_out - amount_out_debt) * constant::B_I1E27 /
872 (debt_i_reserve_in + amount_in_debt),
873 )
874 } else {
875 (
876 debt_i_reserve_in * constant::B_I1E27 / debt_i_reserve_out,
877 (debt_i_reserve_in + amount_in_debt) * constant::B_I1E27 /
878 (debt_i_reserve_out - amount_out_debt),
879 )
880 };
881
882 let price_diff = old_price.abs_diff(new_price);
883 let max_price_diff = old_price * constant::MAX_PRICE_DIFF / constant::TWO_DECIMALS;
884
885 if price_diff > max_price_diff {
886 return Err(SwapError::InsufficientMaxPrice);
887 }
888
889 if trigger_update_col_reserves {
890 update_collateral_reserves_and_limits(
891 swap0_to_1,
892 amount_in_collateral,
893 amount_out_collateral,
894 col_reserves,
895 current_limits,
896 out_decimals,
897 );
898 }
899
900 if trigger_update_debt_reserves {
901 update_debt_reserves_and_limits(
902 swap0_to_1,
903 amount_in_debt,
904 amount_out_debt,
905 debt_reserves,
906 current_limits,
907 out_decimals,
908 );
909 }
910
911 Ok(from_adjusted_amount(amount_in_collateral + amount_in_debt, in_decimals))
912}
913
914fn swap_routing_in(t: U256, x: U256, y: U256, x2: U256, y2: U256) -> U256 {
934 let xy_root = (x * y * constant::B_I1E18).root(2);
935 let x2y2_root = (x2 * y2 * constant::B_I1E18).root(2);
936
937 let numerator = y2 * xy_root + t * xy_root - y * x2y2_root;
938 let denominator = xy_root + x2y2_root;
939 numerator / denominator
940}
941
942#[allow(dead_code)]
949fn swap_routing_out(t: U256, x: U256, y: U256, x2: U256, y2: U256) -> U256 {
950 let xy_root = (x * y * constant::B_I1E18).root(2);
951 let x2y2_root = (x2 * y2 * constant::B_I1E18).root(2);
952
953 let numerator = t * xy_root + y * x2y2_root - y2 * xy_root;
954 let denominator = xy_root + x2y2_root;
955
956 numerator / denominator
957}
958
959fn get_amount_out(amount_in: U256, i_reserve_in: U256, i_reserve_out: U256) -> U256 {
960 amount_in * i_reserve_out / (i_reserve_in + amount_in)
961}
962
963#[allow(dead_code)]
967fn get_amount_in(amount_out: U256, i_reserve_in: U256, i_reserve_out: U256) -> U256 {
968 amount_out * i_reserve_in / (i_reserve_out - amount_out)
969}
970
971fn to_adjusted_amount(amount: U256, decimals: i64) -> U256 {
972 let diff = decimals - constant::DEX_AMOUNT_DECIMALS;
973 if diff == 0 {
974 amount
975 } else if diff > 0 {
976 amount / ten_pow(diff)
977 } else {
978 amount * ten_pow(-diff)
979 }
980}
981
982fn from_adjusted_amount(adjusted_amount: U256, decimals: i64) -> U256 {
992 let diff = decimals - constant::DEX_AMOUNT_DECIMALS;
993
994 if diff == 0 {
995 adjusted_amount
996 } else if diff < 0 {
997 let divisor = ten_pow(-diff);
999 adjusted_amount / divisor
1000 } else {
1001 let multiplier = ten_pow(diff);
1003 adjusted_amount * multiplier
1004 }
1005}
1006
1007fn ten_pow(v: i64) -> U256 {
1008 U256::from(10u64).pow(U256::from((v) as u64))
1009}
1010
1011fn verify_token0_reserves(token0_reserves: U256, token1_reserves: U256, price: U256) -> bool {
1028 let numerator = token1_reserves.saturating_mul(constant::B_I1E27);
1029 let denominator = price.saturating_mul(constant::MIN_SWAP_LIQUIDITY);
1030 token0_reserves >=
1031 numerator
1032 .checked_div(denominator)
1033 .unwrap_or(U256::ZERO)
1034}
1035
1036fn verify_token1_reserves(token0_reserves: U256, token1_reserves: U256, price: U256) -> bool {
1053 let numerator = token0_reserves.saturating_mul(price);
1054 let denominator = constant::B_I1E27.saturating_mul(constant::MIN_SWAP_LIQUIDITY);
1055 token1_reserves >= numerator.div_euclid(&denominator)
1056}
1057
1058fn get_expanded_limit(sync_time: u64, limit: &TokenLimit) -> U256 {
1070 let current_time = SystemTime::now()
1071 .duration_since(UNIX_EPOCH)
1072 .expect("system time before UNIX_EPOCH")
1073 .as_secs();
1074
1075 let elapsed_time = current_time.saturating_sub(sync_time);
1076 let elapsed = U256::from(elapsed_time);
1077
1078 if elapsed_time < 10 {
1079 return limit.available;
1081 }
1082
1083 if elapsed >= limit.expand_duration {
1084 return limit.expands_to;
1086 }
1087
1088 let delta = limit
1091 .expands_to
1092 .saturating_sub(limit.available);
1093 limit
1094 .available
1095 .saturating_add(delta.saturating_mul(elapsed) / limit.expand_duration)
1096}
1097
1098fn update_collateral_reserves_and_limits(
1103 swap0_to_1: bool,
1104 amount_in: U256,
1105 amount_out: U256,
1106 col_reserves: &mut CollateralReserves,
1107 limits: &mut DexLimits,
1108 out_decimals: i64,
1109) {
1110 let unadjusted_amount_out = from_adjusted_amount(amount_out, out_decimals);
1111
1112 if swap0_to_1 {
1113 col_reserves.token0_real_reserves = col_reserves
1115 .token0_real_reserves
1116 .saturating_add(amount_in);
1117 col_reserves.token0_imaginary_reserves = col_reserves
1118 .token0_imaginary_reserves
1119 .saturating_add(amount_in);
1120 col_reserves.token1_real_reserves = col_reserves
1121 .token1_real_reserves
1122 .saturating_sub(amount_out);
1123 col_reserves.token1_imaginary_reserves = col_reserves
1124 .token1_imaginary_reserves
1125 .saturating_sub(amount_out);
1126
1127 limits.withdrawable_token1.available = limits
1128 .withdrawable_token1
1129 .available
1130 .saturating_sub(unadjusted_amount_out);
1131 limits.withdrawable_token1.expands_to = limits
1132 .withdrawable_token1
1133 .expands_to
1134 .saturating_sub(unadjusted_amount_out);
1135 } else {
1136 col_reserves.token0_real_reserves = col_reserves
1138 .token0_real_reserves
1139 .saturating_sub(amount_out);
1140 col_reserves.token0_imaginary_reserves = col_reserves
1141 .token0_imaginary_reserves
1142 .saturating_sub(amount_out);
1143 col_reserves.token1_real_reserves = col_reserves
1144 .token1_real_reserves
1145 .saturating_add(amount_in);
1146 col_reserves.token1_imaginary_reserves = col_reserves
1147 .token1_imaginary_reserves
1148 .saturating_add(amount_in);
1149
1150 limits.withdrawable_token0.available = limits
1151 .withdrawable_token0
1152 .available
1153 .saturating_sub(unadjusted_amount_out);
1154 limits.withdrawable_token0.expands_to = limits
1155 .withdrawable_token0
1156 .expands_to
1157 .saturating_sub(unadjusted_amount_out);
1158 }
1159}
1160
1161fn update_debt_reserves_and_limits(
1162 swap0_to1: bool,
1163 amount_in: U256,
1164 amount_out: U256,
1165 debt_reserves: &mut DebtReserves,
1166 limits: &mut DexLimits,
1167 out_decimals: i64,
1168) {
1169 let unadjusted_amount_out = from_adjusted_amount(amount_out, out_decimals);
1170
1171 if swap0_to1 {
1172 debt_reserves.token0_real_reserves += amount_in;
1173 debt_reserves.token0_imaginary_reserves += amount_in;
1174 debt_reserves.token1_real_reserves -= amount_out;
1175 debt_reserves.token1_imaginary_reserves -= amount_out;
1176
1177 limits.borrowable_token1.available -= unadjusted_amount_out;
1186 limits.borrowable_token1.expands_to -= unadjusted_amount_out;
1187 } else {
1188 debt_reserves.token0_real_reserves -= amount_out;
1189 debt_reserves.token0_imaginary_reserves -= amount_out;
1190 debt_reserves.token1_real_reserves += amount_in;
1191 debt_reserves.token1_imaginary_reserves += amount_in;
1192
1193 limits.borrowable_token0.available -= unadjusted_amount_out;
1194 limits.borrowable_token0.expands_to -= unadjusted_amount_out;
1195 }
1196}
1197
1198fn get_max_reserves(
1199 decimals: u8,
1200 withdrawable_limit: &TokenLimit,
1201 borrowable_limit: &TokenLimit,
1202 real_col_reserves: &U256,
1203 real_debt_reserves: &U256,
1204) -> U256 {
1205 let mut max_limit_reserves = borrowable_limit.expands_to;
1207
1208 if borrowable_limit.expands_to != withdrawable_limit.expands_to {
1209 max_limit_reserves += withdrawable_limit.expands_to;
1210 }
1211
1212 let mut max_real_reserves = *real_col_reserves + *real_debt_reserves;
1214
1215 if decimals > constant::DEX_AMOUNT_DECIMALS as u8 {
1216 let diff = decimals as i64 - constant::DEX_AMOUNT_DECIMALS;
1217 max_real_reserves *= ten_pow(diff);
1218 } else if decimals < constant::DEX_AMOUNT_DECIMALS as u8 {
1219 let diff = constant::DEX_AMOUNT_DECIMALS - decimals as i64;
1220 max_real_reserves /= ten_pow(diff);
1221 }
1222
1223 if max_real_reserves < max_limit_reserves {
1225 max_real_reserves
1226 } else {
1227 max_limit_reserves
1228 }
1229}
1230
1231#[cfg(test)]
1232mod test {
1233 use std::str::FromStr;
1234
1235 use alloy::primitives::I256;
1236 use anyhow::bail;
1237 use num_traits::Num;
1238 use tycho_common::models::Chain;
1239
1240 use super::*;
1241
1242 fn setup_fluid_pool(center_price: U256) -> (Token, Token, FluidV1) {
1243 let wsteth = Token::new(
1244 &Bytes::from_str("0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0").unwrap(),
1245 "wsteth",
1246 18,
1247 0,
1248 &[Some(20000)],
1249 Chain::Ethereum,
1250 100,
1251 );
1252 let eth = Token::new(
1253 &Bytes::from_str("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE").unwrap(),
1254 "ETH",
1255 18,
1256 0,
1257 &[Some(2000)],
1258 Chain::Ethereum,
1259 100,
1260 );
1261
1262 let pool = FluidV1::new(
1263 &Bytes::from_str("0x0B1a513ee24972DAEf112bC777a5610d4325C9e7").unwrap(),
1264 &wsteth,
1265 ð,
1266 CollateralReserves {
1267 token0_real_reserves: U256::from_str("2169934539358").unwrap(),
1268 token1_real_reserves: U256::from_str("19563846299171").unwrap(),
1269 token0_imaginary_reserves: U256::from_str("62490032619260838").unwrap(),
1270 token1_imaginary_reserves: U256::from_str("73741038977020279").unwrap(),
1271 },
1272 DebtReserves {
1273 token0_real_reserves: U256::from_str("2169108220421").unwrap(),
1274 token1_real_reserves: U256::from_str("19572550738602").unwrap(),
1275 token0_imaginary_reserves: U256::from_str("62511862774117387").unwrap(),
1276 token1_imaginary_reserves: U256::from_str("73766803277429176").unwrap(),
1277 },
1278 limits_wide(),
1279 center_price,
1280 U256::from_str("100").unwrap(),
1281 SystemTime::now()
1282 .duration_since(UNIX_EPOCH)
1283 .unwrap()
1284 .as_secs() -
1285 10,
1286 );
1287 (wsteth, eth, pool)
1288 }
1289
1290 fn limits_wide() -> DexLimits {
1291 let limit_wide = U256::from_str("34242332879776515083099999").unwrap();
1292 DexLimits {
1293 withdrawable_token0: TokenLimit {
1294 available: limit_wide,
1295 expands_to: limit_wide,
1296 expand_duration: U256::ZERO,
1297 },
1298 withdrawable_token1: TokenLimit {
1299 available: limit_wide,
1300 expands_to: limit_wide,
1301 expand_duration: U256::from(22),
1302 },
1303 borrowable_token0: TokenLimit {
1304 available: limit_wide,
1305 expands_to: limit_wide,
1306 expand_duration: U256::ZERO,
1307 },
1308 borrowable_token1: TokenLimit {
1309 available: limit_wide,
1310 expands_to: limit_wide,
1311 expand_duration: U256::from(22),
1312 },
1313 }
1314 }
1315
1316 fn limits_tight() -> DexLimits {
1317 let limit_expand_tight = U256::from_str("711907234052361388866").unwrap();
1318
1319 DexLimits {
1320 withdrawable_token0: TokenLimit {
1321 available: U256::from_str("456740438880263").unwrap(),
1322 expands_to: limit_expand_tight,
1323 expand_duration: U256::from(600),
1324 },
1325 withdrawable_token1: TokenLimit {
1326 available: U256::from_str("825179383432029").unwrap(),
1327 expands_to: limit_expand_tight,
1328 expand_duration: U256::from(600),
1329 },
1330 borrowable_token0: TokenLimit {
1331 available: U256::from_str("941825058374170").unwrap(),
1332 expands_to: limit_expand_tight,
1333 expand_duration: U256::from(600),
1334 },
1335 borrowable_token1: TokenLimit {
1336 available: U256::from_str("941825058374170").unwrap(),
1337 expands_to: limit_expand_tight,
1338 expand_duration: U256::from(600),
1339 },
1340 }
1341 }
1342 fn new_col_reserves_one() -> CollateralReserves {
1343 CollateralReserves {
1344 token0_real_reserves: U256::from_str("20000000006000000").unwrap(),
1345 token1_real_reserves: U256::from_str("20000000000500000").unwrap(),
1346 token0_imaginary_reserves: U256::from_str("389736659726997981").unwrap(),
1347 token1_imaginary_reserves: U256::from_str("389736659619871949").unwrap(),
1348 }
1349 }
1350
1351 fn new_col_reserves_empty() -> CollateralReserves {
1352 CollateralReserves {
1353 token0_real_reserves: U256::ZERO,
1354 token1_real_reserves: U256::ZERO,
1355 token0_imaginary_reserves: U256::ZERO,
1356 token1_imaginary_reserves: U256::ZERO,
1357 }
1358 }
1359
1360 fn new_debt_reserves_empty() -> DebtReserves {
1361 DebtReserves {
1362 token0_real_reserves: U256::ZERO,
1363 token1_real_reserves: U256::ZERO,
1364 token0_imaginary_reserves: U256::ZERO,
1365 token1_imaginary_reserves: U256::ZERO,
1366 }
1367 }
1368
1369 fn new_debt_reserves_one() -> DebtReserves {
1370 DebtReserves {
1371 token0_real_reserves: U256::from_str("9486832995556050").unwrap(),
1372 token1_real_reserves: U256::from_str("9486832993079885").unwrap(),
1373 token0_imaginary_reserves: U256::from_str("184868330099560759").unwrap(),
1374 token1_imaginary_reserves: U256::from_str("184868330048879109").unwrap(),
1375 }
1376 }
1377
1378 pub fn get_approx_center_price_in(
1379 amount_to_swap: U256,
1380 swap0_to_1: bool,
1381 col_reserves: &CollateralReserves,
1382 debt_reserves: &DebtReserves,
1383 ) -> Result<U256, anyhow::Error> {
1384 let col_pool_enabled = !col_reserves
1385 .token0_real_reserves
1386 .is_zero() &&
1387 !col_reserves
1388 .token1_real_reserves
1389 .is_zero() &&
1390 !col_reserves
1391 .token0_imaginary_reserves
1392 .is_zero() &&
1393 !col_reserves
1394 .token1_imaginary_reserves
1395 .is_zero();
1396
1397 let debt_pool_enabled = !debt_reserves
1398 .token0_real_reserves
1399 .is_zero() &&
1400 !debt_reserves
1401 .token1_real_reserves
1402 .is_zero() &&
1403 !debt_reserves
1404 .token0_imaginary_reserves
1405 .is_zero() &&
1406 !debt_reserves
1407 .token1_imaginary_reserves
1408 .is_zero();
1409
1410 let (col_i_reserve_in, col_i_reserve_out, debt_i_reserve_in, debt_i_reserve_out) =
1411 if swap0_to_1 {
1412 (
1413 col_reserves.token0_imaginary_reserves,
1414 col_reserves.token1_imaginary_reserves,
1415 debt_reserves.token0_imaginary_reserves,
1416 debt_reserves.token1_imaginary_reserves,
1417 )
1418 } else {
1419 (
1420 col_reserves.token1_imaginary_reserves,
1421 col_reserves.token0_imaginary_reserves,
1422 debt_reserves.token1_imaginary_reserves,
1423 debt_reserves.token0_imaginary_reserves,
1424 )
1425 };
1426
1427 let a = if col_pool_enabled && debt_pool_enabled {
1428 swap_routing_in(
1429 amount_to_swap,
1430 col_i_reserve_out,
1431 col_i_reserve_in,
1432 debt_i_reserve_out,
1433 debt_i_reserve_in,
1434 )
1435 } else if debt_pool_enabled {
1436 U256::MAX } else if col_pool_enabled {
1438 amount_to_swap
1439 .checked_add(U256::from(1))
1440 .unwrap()
1441 } else {
1442 bail!("No pools are enabled");
1443 };
1444
1445 let (amount_in_collateral, amount_in_debt) = if a == U256::MAX || a == U256::ZERO {
1446 (U256::ZERO, amount_to_swap)
1447 } else if a >= amount_to_swap {
1448 (amount_to_swap, U256::ZERO)
1449 } else {
1450 (a, amount_to_swap - a)
1451 };
1452
1453 let price = if amount_in_collateral > amount_in_debt {
1454 if swap0_to_1 {
1455 col_i_reserve_out
1456 .checked_mul(constant::B_I1E27)
1457 .unwrap() /
1458 col_i_reserve_in
1459 } else {
1460 col_i_reserve_in
1461 .checked_mul(constant::B_I1E27)
1462 .unwrap() /
1463 col_i_reserve_out
1464 }
1465 } else if swap0_to_1 {
1466 debt_i_reserve_out
1467 .checked_mul(constant::B_I1E27)
1468 .unwrap() /
1469 debt_i_reserve_in
1470 } else {
1471 debt_i_reserve_in
1472 .checked_mul(constant::B_I1E27)
1473 .unwrap() /
1474 debt_i_reserve_out
1475 };
1476
1477 Ok(price)
1478 }
1479
1480 pub fn get_approx_center_price_out(
1481 amount_out: U256,
1482 swap0_to_1: bool,
1483 col_reserves: &CollateralReserves,
1484 debt_reserves: &DebtReserves,
1485 ) -> Result<U256, SwapError> {
1486 let col_pool_enabled = col_reserves.token0_real_reserves > U256::ZERO &&
1487 col_reserves.token1_real_reserves > U256::ZERO &&
1488 col_reserves.token0_imaginary_reserves > U256::ZERO &&
1489 col_reserves.token1_imaginary_reserves > U256::ZERO;
1490
1491 let debt_pool_enabled = debt_reserves.token0_real_reserves > U256::ZERO &&
1492 debt_reserves.token1_real_reserves > U256::ZERO &&
1493 debt_reserves.token0_imaginary_reserves > U256::ZERO &&
1494 debt_reserves.token1_imaginary_reserves > U256::ZERO;
1495
1496 let (col_i_reserve_in, col_i_reserve_out, debt_i_reserve_in, debt_i_reserve_out) =
1497 if swap0_to_1 {
1498 (
1499 col_reserves.token0_imaginary_reserves,
1500 col_reserves.token1_imaginary_reserves,
1501 debt_reserves.token0_imaginary_reserves,
1502 debt_reserves.token1_imaginary_reserves,
1503 )
1504 } else {
1505 (
1506 col_reserves.token1_imaginary_reserves,
1507 col_reserves.token0_imaginary_reserves,
1508 debt_reserves.token1_imaginary_reserves,
1509 debt_reserves.token0_imaginary_reserves,
1510 )
1511 };
1512
1513 let a = if col_pool_enabled && debt_pool_enabled {
1514 swap_routing_out(
1515 amount_out,
1516 col_i_reserve_in,
1517 col_i_reserve_out,
1518 debt_i_reserve_in,
1519 debt_i_reserve_out,
1520 )
1521 } else if debt_pool_enabled {
1522 U256::MAX } else if col_pool_enabled {
1524 amount_out + U256::ONE } else {
1526 return Err(SwapError::NoPoolsEnabled);
1527 };
1528
1529 let mut amount_in_collateral = U256::ZERO;
1530 let mut amount_in_debt = U256::ZERO;
1531
1532 if a <= U256::ZERO {
1533 amount_in_debt = get_amount_in(amount_out, debt_i_reserve_in, debt_i_reserve_out);
1534 } else if a >= amount_out {
1535 amount_in_collateral = get_amount_in(amount_out, col_i_reserve_in, col_i_reserve_out);
1536 } else {
1537 amount_in_collateral = get_amount_in(a, col_i_reserve_in, col_i_reserve_out);
1538 amount_in_debt = get_amount_in(amount_out - a, debt_i_reserve_in, debt_i_reserve_out);
1539 }
1540
1541 let price = if amount_in_collateral > amount_in_debt {
1542 if swap0_to_1 {
1543 col_i_reserve_out * constant::B_I1E27 / col_i_reserve_in
1544 } else {
1545 col_i_reserve_in * constant::B_I1E27 / col_i_reserve_out
1546 }
1547 } else if swap0_to_1 {
1548 debt_i_reserve_out * constant::B_I1E27 / debt_i_reserve_in
1549 } else {
1550 debt_i_reserve_in * constant::B_I1E27 / debt_i_reserve_out
1551 };
1552
1553 Ok(price)
1554 }
1555
1556 #[test]
1557 fn test_calc_amount_out_zero2one() {
1558 let (wsteth, eth, pool) = setup_fluid_pool(U256::ONE);
1559 let cases = [
1560 ("1000000000000000000", "1179917402128000000"),
1561 ("500000000000000000", "589961060629000000"),
1562 ];
1563 for (amount_in_str, exp_out_str) in cases.into_iter() {
1564 let exp_out = BigUint::from_str_radix(exp_out_str, 10).unwrap();
1565 let res = pool
1566 .get_amount_out(BigUint::from_str_radix(amount_in_str, 10).unwrap(), &wsteth, ð)
1567 .unwrap();
1568
1569 assert_eq!(res.amount, exp_out);
1570 }
1571 }
1572
1573 #[test]
1574 fn test_calc_amount_out_one2zero() {
1575 let center_price = U256::from_str("1200000000000000000000000000").unwrap();
1576 let (wsteth, eth, pool) = setup_fluid_pool(center_price);
1577 let cases = [("800000000000000000", "677868867152000000")];
1578 for (amount_in_str, exp_out_str) in cases.into_iter() {
1579 let exp_out = BigUint::from_str_radix(exp_out_str, 10).unwrap();
1580 let res = pool
1581 .get_amount_out(BigUint::from_str_radix(amount_in_str, 10).unwrap(), ð, &wsteth)
1582 .unwrap();
1583
1584 assert_eq!(res.amount, exp_out);
1585 }
1586 }
1587
1588 #[test]
1589 fn test_amount_out_exceeds_reserve() {
1590 let (wsteth, eth, mut pool) = setup_fluid_pool(U256::ONE);
1591 pool.pool_reserve0 = U256::from_str("18760613183894").unwrap();
1593 pool.pool_reserve1 = U256::from_str("22123580158026").unwrap();
1594 let amount_in = BigUint::from_str_radix("30000000000000000000", 10).unwrap(); let result = pool.get_amount_out(amount_in, &wsteth, ð);
1596
1597 assert!(result.is_err(), "Expected an error for exceeding reserves");
1598 assert_eq!(
1599 result.unwrap_err().to_string(),
1600 SimulationError::from(SwapError::InsufficientReserve).to_string()
1601 );
1602 }
1603
1604 #[test]
1605 fn test_swap_in() {
1606 let sync_time = SystemTime::now()
1607 .duration_since(UNIX_EPOCH)
1608 .unwrap()
1609 .as_secs();
1610
1611 assert_swap_in_result(
1612 true,
1613 U256::from(1_000_000_000_000_000u128), new_col_reserves_one(),
1615 new_debt_reserves_one(),
1616 "998262697204710000000",
1617 12,
1618 18,
1619 limits_wide(),
1620 sync_time - 10,
1621 );
1622
1623 assert_swap_in_result(
1624 true,
1625 U256::from(1_000_000_000_000_000u128),
1626 new_col_reserves_empty(),
1627 new_debt_reserves_one(),
1628 "994619847016724000000",
1629 12,
1630 18,
1631 limits_wide(),
1632 sync_time - 10,
1633 );
1634
1635 assert_swap_in_result(
1636 true,
1637 U256::from(1_000_000_000_000_000u128),
1638 new_col_reserves_one(),
1639 new_debt_reserves_empty(),
1640 "997440731289905000000",
1641 12,
1642 18,
1643 limits_wide(),
1644 sync_time - 10,
1645 );
1646
1647 assert_swap_in_result(
1648 false,
1649 U256::from(1_000_000_000_000_000u128),
1650 new_col_reserves_one(),
1651 new_debt_reserves_one(),
1652 "998262697752553000000",
1653 12,
1654 18,
1655 limits_wide(),
1656 sync_time - 10,
1657 );
1658
1659 assert_swap_in_result(
1660 false,
1661 U256::from(1_000_000_000_000_000u128),
1662 new_col_reserves_empty(),
1663 new_debt_reserves_one(),
1664 "994619847560607000000",
1665 12,
1666 18,
1667 limits_wide(),
1668 sync_time - 10,
1669 );
1670
1671 assert_swap_in_result(
1672 false,
1673 U256::from(1_000_000_000_000_000u128),
1674 new_col_reserves_one(),
1675 new_debt_reserves_empty(),
1676 "997440731837532000000",
1677 12,
1678 18,
1679 limits_wide(),
1680 sync_time - 10,
1681 );
1682 }
1683
1684 #[allow(clippy::too_many_arguments)]
1697 fn assert_swap_in_result(
1698 swap0_to_1: bool,
1699 amount_in: U256,
1700 mut col_reserves: CollateralReserves,
1701 mut debt_reserves: DebtReserves,
1702 expected_amount_out: &str,
1703 in_decimals: i64,
1704 out_decimals: i64,
1705 mut limits: DexLimits,
1706 sync_time: u64,
1707 ) {
1708 let price =
1709 get_approx_center_price_in(amount_in, swap0_to_1, &col_reserves, &debt_reserves)
1710 .expect("Failed to get approx center price");
1711
1712 let adjusted_amount_in = to_adjusted_amount(amount_in, in_decimals);
1713 let out_amt = swap_in_adjusted(
1714 swap0_to_1,
1715 adjusted_amount_in,
1716 &mut col_reserves,
1717 &mut debt_reserves,
1718 out_decimals,
1719 &mut limits,
1720 price,
1721 sync_time,
1722 )
1723 .expect("Failed to calculate swap in adjusted");
1724
1725 assert_eq!(expected_amount_out, out_amt.to_string(), "Amount out mismatch");
1726 }
1727
1728 #[allow(clippy::too_many_arguments)]
1729 fn assert_swap_out_result(
1730 swap0_to_1: bool,
1731 amount_out: U256,
1732 mut col_reserves: CollateralReserves,
1733 mut debt_reserves: DebtReserves,
1734 expected_amount_in: &str,
1735 in_decimals: i64,
1736 out_decimals: i64,
1737 mut limits: DexLimits,
1738 sync_time: i64,
1739 ) {
1740 let price =
1741 get_approx_center_price_out(amount_out, swap0_to_1, &col_reserves, &debt_reserves)
1742 .expect("failed to get approx center price");
1743
1744 let in_amt = swap_out_adjusted(
1745 swap0_to_1,
1746 to_adjusted_amount(amount_out, out_decimals),
1747 &mut col_reserves,
1748 &mut debt_reserves,
1749 in_decimals,
1750 out_decimals,
1751 &mut limits,
1752 price,
1753 sync_time as u64,
1754 )
1755 .expect("swap_out_adjusted failed");
1756
1757 assert_eq!(expected_amount_in, from_adjusted_amount(in_amt, in_decimals).to_string());
1758 }
1759
1760 #[test]
1761 fn test_swap_in_limits() {
1762 let sync_time = SystemTime::now()
1763 .duration_since(UNIX_EPOCH)
1764 .unwrap()
1765 .as_secs();
1766
1767 let price = get_approx_center_price_in(
1769 U256::from(1_000_000_000_000_000u128),
1770 true,
1771 &new_col_reserves_one(),
1772 &new_debt_reserves_one(),
1773 )
1774 .unwrap();
1775
1776 let res = swap_in_adjusted(
1777 true,
1778 U256::from(1_000_000_000_000_000u128),
1779 &mut new_col_reserves_one(),
1780 &mut new_debt_reserves_one(),
1781 18,
1782 &mut limits_tight(),
1783 price,
1784 sync_time - 10,
1785 );
1786
1787 assert_eq!(res.unwrap_err().to_string(), SwapError::InsufficientBorrowable.to_string());
1788
1789 let price = get_approx_center_price_out(
1791 U256::from(1_000_000_000_000_000u128),
1792 true,
1793 &new_col_reserves_one(),
1794 &new_debt_reserves_one(),
1795 )
1796 .unwrap();
1797
1798 let out_amt = swap_in_adjusted(
1799 true,
1800 U256::from(1_000_000_000_000_000u128),
1801 &mut new_col_reserves_one(),
1802 &mut new_debt_reserves_one(),
1803 18,
1804 &mut limits_tight(),
1805 price,
1806 sync_time - 6000,
1807 )
1808 .unwrap();
1809
1810 assert_eq!(out_amt.to_string(), "998262697204710000000");
1811
1812 let price = get_approx_center_price_out(
1814 U256::from(30_000_000_000_000_000u128),
1815 true,
1816 &new_col_reserves_one(),
1817 &new_debt_reserves_one(),
1818 )
1819 .unwrap();
1820
1821 let res = swap_in_adjusted(
1822 true,
1823 U256::from(30_000_000_000_000_000u128),
1824 &mut new_col_reserves_one(),
1825 &mut new_debt_reserves_one(),
1826 18,
1827 &mut limits_wide(),
1828 price,
1829 sync_time - 10,
1830 );
1831
1832 assert_eq!(res.unwrap_err().to_string(), SwapError::InsufficientMaxPrice.to_string());
1833
1834 let price = get_approx_center_price_out(
1836 U256::from(50_000_000_000_000_000u128),
1837 true,
1838 &new_col_reserves_one(),
1839 &new_debt_reserves_one(),
1840 )
1841 .unwrap();
1842
1843 let res = swap_in_adjusted(
1844 true,
1845 U256::from(50_000_000_000_000_000u128),
1846 &mut new_col_reserves_one(),
1847 &mut new_debt_reserves_one(),
1848 18,
1849 &mut limits_wide(),
1850 price,
1851 sync_time - 10,
1852 );
1853
1854 assert_eq!(res.unwrap_err().to_string(), SwapError::InsufficientReserve.to_string());
1855 }
1856
1857 #[test]
1858 fn test_swap_in_adjusted_compare_estimate_in() {
1859 let now = SystemTime::now()
1860 .duration_since(UNIX_EPOCH)
1861 .unwrap()
1862 .as_secs();
1863 let expected_amount_out = U256::from_str("1180035404724000000").unwrap();
1864 let mut col_reserves = CollateralReserves {
1865 token0_real_reserves: U256::from_str("2169934539358").unwrap(),
1866 token1_real_reserves: U256::from_str("19563846299171").unwrap(),
1867 token0_imaginary_reserves: U256::from_str("62490032619260838").unwrap(),
1868 token1_imaginary_reserves: U256::from_str("73741038977020279").unwrap(),
1869 };
1870 let mut debt_reserves = DebtReserves {
1871 token0_real_reserves: U256::from_str("2169108220421").unwrap(),
1872 token1_real_reserves: U256::from_str("19572550738602").unwrap(),
1873 token0_imaginary_reserves: U256::from_str("62511862774117387").unwrap(),
1874 token1_imaginary_reserves: U256::from_str("73766803277429176").unwrap(),
1875 };
1876 let amount_in = U256::from(1000000000000u128); let price = get_approx_center_price_in(amount_in, true, &col_reserves, &debt_reserves)
1878 .expect("Failed to get approximate center price");
1879
1880 let out_amt = swap_in_adjusted(
1881 true,
1882 amount_in,
1883 &mut col_reserves,
1884 &mut debt_reserves,
1885 18,
1886 &mut limits_wide(),
1887 price,
1888 now - 10,
1889 )
1890 .expect("Failed to swap in adjusted");
1891
1892 assert_eq!(expected_amount_out, out_amt);
1893 }
1894
1895 #[test]
1896 fn test_swap_in_debt_empty() {
1897 let now = SystemTime::now()
1898 .duration_since(UNIX_EPOCH)
1899 .unwrap()
1900 .as_secs();
1901
1902 assert_swap_in_result(
1903 true,
1904 U256::from_str("1000000000000000").unwrap(),
1905 new_col_reserves_empty(),
1906 new_debt_reserves_one(),
1907 "994619847016724",
1908 12,
1909 12,
1910 limits_wide(),
1911 now - 10,
1912 );
1913
1914 assert_swap_in_result(
1915 false,
1916 U256::from_str("1000000000000000").unwrap(),
1917 new_col_reserves_empty(),
1918 new_debt_reserves_one(),
1919 "994619847560607",
1920 12,
1921 12,
1922 limits_wide(),
1923 now - 10,
1924 )
1925 }
1926
1927 #[test]
1928 fn test_swap_in_col_empty() {
1929 let now = SystemTime::now()
1930 .duration_since(UNIX_EPOCH)
1931 .unwrap()
1932 .as_secs();
1933
1934 assert_swap_in_result(
1935 true,
1936 U256::from_str("1000000000000000").unwrap(),
1937 new_col_reserves_one(),
1938 new_debt_reserves_empty(),
1939 "997440731289905",
1940 12,
1941 12,
1942 limits_wide(),
1943 now - 10,
1944 );
1945
1946 assert_swap_in_result(
1947 false,
1948 U256::from_str("1000000000000000").unwrap(),
1949 new_col_reserves_one(),
1950 new_debt_reserves_empty(),
1951 "997440731837532",
1952 12,
1953 12,
1954 limits_wide(),
1955 now - 10,
1956 )
1957 }
1958
1959 #[test]
1960 fn test_swap_out() {
1961 let sync_time = (SystemTime::now()
1962 .duration_since(UNIX_EPOCH)
1963 .unwrap()
1964 .as_secs() as i64) -
1965 10;
1966
1967 assert_swap_out_result(
1968 true,
1969 U256::from(1_000_000_000_000_000u64),
1970 new_col_reserves_one(),
1971 new_debt_reserves_one(),
1972 "1001743360284199",
1973 12,
1974 12,
1975 limits_wide(),
1976 sync_time,
1977 );
1978
1979 assert_swap_out_result(
1980 true,
1981 U256::from(1_000_000_000_000_000u64),
1982 new_col_reserves_empty(),
1983 new_debt_reserves_one(),
1984 "1005438674786548",
1985 12,
1986 12,
1987 limits_wide(),
1988 sync_time,
1989 );
1990
1991 assert_swap_out_result(
1992 true,
1993 U256::from(1_000_000_000_000_000u64),
1994 new_col_reserves_one(),
1995 new_debt_reserves_empty(),
1996 "1002572435818386",
1997 12,
1998 12,
1999 limits_wide(),
2000 sync_time,
2001 );
2002
2003 assert_swap_out_result(
2004 false,
2005 U256::from(1_000_000_000_000_000u64),
2006 new_col_reserves_one(),
2007 new_debt_reserves_one(),
2008 "1001743359733488",
2009 12,
2010 12,
2011 limits_wide(),
2012 sync_time,
2013 );
2014
2015 assert_swap_out_result(
2016 false,
2017 U256::from(1_000_000_000_000_000u64),
2018 new_col_reserves_empty(),
2019 new_debt_reserves_one(),
2020 "1005438674233767",
2021 12,
2022 12,
2023 limits_wide(),
2024 sync_time,
2025 );
2026
2027 assert_swap_out_result(
2028 false,
2029 U256::from(1_000_000_000_000_000u64),
2030 new_col_reserves_one(),
2031 new_debt_reserves_empty(),
2032 "1002572435266527",
2033 12,
2034 12,
2035 limits_wide(),
2036 sync_time,
2037 );
2038 }
2039
2040 #[test]
2041 fn test_swap_out_limits() {
2042 let sync_time_recent = (SystemTime::now()
2043 .duration_since(UNIX_EPOCH)
2044 .unwrap()
2045 .as_secs()) -
2046 10;
2047
2048 let sync_time_expanded = sync_time_recent - 5990; let price = get_approx_center_price_out(
2052 U256::from(1_000_000_000_000_000u64),
2053 true,
2054 &new_col_reserves_one(),
2055 &new_debt_reserves_one(),
2056 )
2057 .unwrap();
2058
2059 let result = swap_out_adjusted(
2060 true,
2061 U256::from(1_000_000_000_000_000u64),
2062 &mut new_col_reserves_one(),
2063 &mut new_debt_reserves_one(),
2064 12,
2065 18,
2066 &mut limits_tight(),
2067 price,
2068 sync_time_recent,
2069 );
2070
2071 assert!(matches!(result, Err(SwapError::InsufficientBorrowable)));
2072
2073 let price = get_approx_center_price_out(
2075 U256::from(1_000_000_000_000_000u64),
2076 true,
2077 &new_col_reserves_one(),
2078 &new_debt_reserves_one(),
2079 )
2080 .unwrap();
2081
2082 let result = swap_out_adjusted(
2083 true,
2084 U256::from(1_000_000_000_000_000u64),
2085 &mut new_col_reserves_one(),
2086 &mut new_debt_reserves_one(),
2087 12,
2088 18,
2089 &mut limits_tight(),
2090 price,
2091 sync_time_expanded,
2092 )
2093 .unwrap();
2094
2095 assert_eq!(from_adjusted_amount(result, 12).to_string(), "1001743360284199");
2096
2097 let price = get_approx_center_price_out(
2099 U256::from(20_000_000_000_000_000u64),
2100 true,
2101 &new_col_reserves_one(),
2102 &new_debt_reserves_one(),
2103 )
2104 .unwrap();
2105
2106 let result = swap_out_adjusted(
2107 true,
2108 U256::from(20_000_000_000_000_000u64),
2109 &mut new_col_reserves_one(),
2110 &mut new_debt_reserves_one(),
2111 12,
2112 18,
2113 &mut limits_wide(),
2114 price,
2115 sync_time_recent,
2116 );
2117
2118 assert!(matches!(result, Err(SwapError::InsufficientMaxPrice)));
2119
2120 let price = get_approx_center_price_out(
2122 U256::from(30_000_000_000_000_000u64),
2123 true,
2124 &new_col_reserves_one(),
2125 &new_debt_reserves_one(),
2126 )
2127 .unwrap();
2128
2129 let result = swap_out_adjusted(
2130 true,
2131 U256::from(30_000_000_000_000_000u64),
2132 &mut new_col_reserves_one(),
2133 &mut new_debt_reserves_one(),
2134 12,
2135 18,
2136 &mut limits_wide(),
2137 price,
2138 sync_time_recent,
2139 );
2140
2141 assert!(matches!(result, Err(SwapError::InsufficientReserve)));
2142 }
2143
2144 #[test]
2145 fn test_swap_out_empty_debt() {
2146 let sync_time = (SystemTime::now()
2147 .duration_since(UNIX_EPOCH)
2148 .unwrap()
2149 .as_secs() as i64) -
2150 10;
2151
2152 assert_swap_out_result(
2154 true,
2155 U256::from(994_619_847_016_724u64),
2156 new_col_reserves_empty(),
2157 new_debt_reserves_one(),
2158 "999999999999999",
2159 12,
2160 12,
2161 limits_wide(),
2162 sync_time,
2163 );
2164
2165 assert_swap_out_result(
2167 false,
2168 U256::from(994_619_847_560_607u64),
2169 new_col_reserves_empty(),
2170 new_debt_reserves_one(),
2171 "999999999999999",
2172 12,
2173 12,
2174 limits_wide(),
2175 sync_time,
2176 );
2177 }
2178
2179 #[test]
2180 fn test_swap_out_empty_collateral() {
2181 let sync_time = (SystemTime::now()
2182 .duration_since(UNIX_EPOCH)
2183 .unwrap()
2184 .as_secs() as i64) -
2185 10;
2186
2187 assert_swap_out_result(
2189 true,
2190 U256::from(997_440_731_289_905u64),
2191 new_col_reserves_one(),
2192 new_debt_reserves_empty(),
2193 "999999999999999",
2194 12,
2195 12,
2196 limits_wide(),
2197 sync_time,
2198 );
2199
2200 assert_swap_out_result(
2202 false,
2203 U256::from(997_440_731_837_532u64),
2204 new_col_reserves_one(),
2205 new_debt_reserves_empty(),
2206 "999999999999999",
2207 12,
2208 12,
2209 limits_wide(),
2210 sync_time,
2211 );
2212 }
2213
2214 pub fn new_verify_ratio_col_reserves() -> CollateralReserves {
2215 CollateralReserves {
2216 token0_real_reserves: U256::from(2_000_000u64) * U256::from(10u64).pow(U256::from(12)),
2217 token1_real_reserves: U256::from(15_000u64) * U256::from(10u64).pow(U256::from(12)),
2218 token0_imaginary_reserves: U256::ZERO,
2219 token1_imaginary_reserves: U256::ZERO,
2220 }
2221 }
2222
2223 pub fn new_verify_ratio_debt_reserves() -> DebtReserves {
2224 DebtReserves {
2225 token0_real_reserves: U256::from(2_000_000u64) * U256::from(10u64).pow(U256::from(12)),
2226 token1_real_reserves: U256::from(15_000u64) * U256::from(10u64).pow(U256::from(12)),
2227 token0_imaginary_reserves: U256::ZERO,
2228 token1_imaginary_reserves: U256::ZERO,
2229 }
2230 }
2231
2232 pub fn calculate_reserves_outside_range(
2234 geometric_mean_price: U256,
2235 price_at_range: U256,
2236 reserve_x: U256,
2237 reserve_y: U256,
2238 ) -> (I256, I256) {
2239 let geometric_mean_price = I256::from(geometric_mean_price);
2240 let price_at_range = I256::from(price_at_range);
2241 let reserve_x = I256::from(reserve_x);
2242 let reserve_y = I256::from(reserve_y);
2243
2244 let one_e27 = I256::from(constant::B_I1E27);
2245 let two = I256::try_from(2i8).unwrap();
2246
2247 let part1 = price_at_range
2249 .checked_sub(geometric_mean_price)
2250 .expect("priceAtRange must be >= geometricMeanPrice");
2251
2252 let part2 = geometric_mean_price
2254 .checked_mul(reserve_x)
2255 .unwrap()
2256 .checked_add(reserve_y.checked_mul(one_e27).unwrap())
2257 .unwrap()
2258 .checked_div(two.checked_mul(part1).unwrap())
2259 .unwrap();
2260
2261 let mut part3 = reserve_x
2263 .checked_mul(reserve_y)
2264 .unwrap();
2265
2266 let one_e50 = I256::try_from(10)
2267 .unwrap()
2268 .pow(U256::from(50));
2269
2270 if part3 < one_e50 {
2272 part3 = part3
2273 .checked_mul(one_e27)
2274 .unwrap()
2275 .checked_div(part1)
2276 .unwrap();
2277 } else {
2278 part3 = part3
2279 .checked_div(part1)
2280 .unwrap()
2281 .checked_mul(one_e27)
2282 .unwrap();
2283 }
2284
2285 let part2_squared = part2.checked_mul(part2).unwrap();
2287 let inside_sqrt = part3
2288 .checked_add(part2_squared)
2289 .unwrap();
2290 let sqrt_value = I256::from(
2291 U256::try_from(inside_sqrt)
2292 .unwrap()
2293 .root(2),
2294 );
2295
2296 let reserve_x_outside = part2.checked_add(sqrt_value).unwrap();
2297
2298 let reserve_y_outside = reserve_x_outside
2300 .checked_mul(geometric_mean_price)
2301 .unwrap()
2302 .checked_div(one_e27)
2303 .unwrap();
2304
2305 (reserve_x_outside, reserve_y_outside)
2306 }
2307
2308 #[test]
2309 fn test_swap_in_verify_reserves_in_range() {
2310 let decimals: i64 = 6;
2311 let mut col_reserves = new_verify_ratio_col_reserves();
2312 let mut debt_reserves = new_verify_ratio_debt_reserves();
2313
2314 let mut price = U256::from_str("1000001000000000000000000000").unwrap();
2315
2316 let (reserve_x_outside, reserve_y_outside) = calculate_reserves_outside_range(
2318 constant::B_I1E27,
2319 price,
2320 col_reserves.token0_real_reserves,
2321 col_reserves.token1_real_reserves,
2322 );
2323
2324 col_reserves.token0_imaginary_reserves =
2325 U256::from(reserve_x_outside + I256::from(col_reserves.token0_real_reserves));
2326 col_reserves.token1_imaginary_reserves = U256::from(
2327 I256::from(reserve_y_outside) + I256::from(col_reserves.token1_real_reserves),
2328 );
2329
2330 let (reserve_x_outside, reserve_y_outside) = calculate_reserves_outside_range(
2332 constant::B_I1E27,
2333 price,
2334 debt_reserves.token0_real_reserves,
2335 debt_reserves.token1_real_reserves,
2336 );
2337
2338 debt_reserves.token0_imaginary_reserves =
2339 U256::from(reserve_x_outside + I256::from(debt_reserves.token0_real_reserves));
2340 debt_reserves.token1_imaginary_reserves = U256::from(
2341 I256::from(reserve_y_outside) + I256::from(debt_reserves.token1_real_reserves),
2342 );
2343
2344 let sync_time = SystemTime::now()
2345 .duration_since(UNIX_EPOCH)
2346 .unwrap()
2347 .as_secs() -
2348 10;
2349
2350 let swap_amount = U256::from(14_905) * U256::from(10).pow(U256::from(12)); price = get_approx_center_price_in(
2353 swap_amount,
2354 true,
2355 &col_reserves,
2356 &new_debt_reserves_empty(),
2357 )
2358 .unwrap();
2359 let result = swap_in_adjusted(
2360 true,
2361 swap_amount,
2362 &mut col_reserves,
2363 &mut new_debt_reserves_empty(),
2364 decimals,
2365 &mut limits_wide(),
2366 price,
2367 sync_time,
2368 );
2369 assert!(
2370 result.is_err(),
2371 "FAIL: reserves ratio revert NOT hit for col reserves when swap amount 14_905"
2372 );
2373
2374 price = get_approx_center_price_in(
2375 swap_amount,
2376 true,
2377 &new_col_reserves_empty(),
2378 &debt_reserves,
2379 )
2380 .unwrap();
2381 let result = swap_in_adjusted(
2382 true,
2383 swap_amount,
2384 &mut new_col_reserves_empty(),
2385 &mut debt_reserves,
2386 decimals,
2387 &mut limits_wide(),
2388 price,
2389 sync_time,
2390 );
2391 assert!(
2392 result.is_err(),
2393 "FAIL: reserves ratio revert NOT hit for debt reserves when swap amount 14_905"
2394 );
2395
2396 col_reserves = new_verify_ratio_col_reserves();
2398 debt_reserves = new_verify_ratio_debt_reserves();
2399
2400 let (reserve_x_outside, reserve_y_outside) = calculate_reserves_outside_range(
2401 constant::B_I1E27,
2402 price,
2403 col_reserves.token0_real_reserves,
2404 col_reserves.token1_real_reserves,
2405 );
2406
2407 col_reserves.token0_imaginary_reserves =
2408 U256::from(reserve_x_outside + I256::from(col_reserves.token0_real_reserves));
2409 col_reserves.token1_imaginary_reserves = U256::from(
2410 I256::from(reserve_y_outside) + I256::from(col_reserves.token1_real_reserves),
2411 );
2412
2413 let (reserve_x_outside, reserve_y_outside) = calculate_reserves_outside_range(
2414 constant::B_I1E27,
2415 price,
2420 debt_reserves.token0_real_reserves,
2421 debt_reserves.token1_real_reserves,
2422 );
2423 debt_reserves.token0_imaginary_reserves =
2424 U256::from(reserve_x_outside + I256::from(debt_reserves.token0_real_reserves));
2425 debt_reserves.token1_imaginary_reserves = U256::from(
2426 I256::from(reserve_y_outside) + I256::from(debt_reserves.token1_real_reserves),
2427 );
2428
2429 let swap_amount = U256::from(14_895) * U256::from(10).pow(U256::from(12));
2431
2432 price = get_approx_center_price_in(
2433 swap_amount,
2434 true,
2435 &col_reserves,
2436 &new_debt_reserves_empty(),
2437 )
2438 .unwrap();
2439 let result = swap_in_adjusted(
2440 true,
2441 swap_amount,
2442 &mut col_reserves,
2443 &mut new_debt_reserves_empty(),
2444 decimals,
2445 &mut limits_wide(),
2446 price,
2447 sync_time,
2448 );
2449 assert!(
2450 result.is_ok(),
2451 "FAIL: reserves ratio revert hit for col reserves when swap amount 14_895"
2452 );
2453
2454 price = get_approx_center_price_in(
2455 swap_amount,
2456 true,
2457 &new_col_reserves_empty(),
2458 &debt_reserves,
2459 )
2460 .unwrap();
2461 let result = swap_in_adjusted(
2462 true,
2463 swap_amount,
2464 &mut new_col_reserves_empty(),
2465 &mut debt_reserves,
2466 decimals,
2467 &mut limits_wide(),
2468 price,
2469 sync_time,
2470 );
2471 assert!(
2472 result.is_ok(),
2473 "FAIL: reserves ratio revert hit for debt reserves when swap amount 14_895"
2474 );
2475 }
2476
2477 pub fn new_verify_ratio_col_reserves_swap_out() -> CollateralReserves {
2478 CollateralReserves {
2479 token0_real_reserves: U256::from(15_000u64) * U256::from(10u64).pow(U256::from(12)), token1_real_reserves: U256::from(2_000_000u64) * U256::from(10u64).pow(U256::from(12)), token0_imaginary_reserves: U256::ZERO,
2482 token1_imaginary_reserves: U256::ZERO,
2483 }
2484 }
2485
2486 pub fn new_verify_ratio_debt_reserves_swap_out() -> DebtReserves {
2487 DebtReserves {
2488 token0_real_reserves: U256::from(15_000u64) * U256::from(10u64).pow(U256::from(12)),
2489 token1_real_reserves: U256::from(2_000_000u64) * U256::from(10u64).pow(U256::from(12)),
2490 token0_imaginary_reserves: U256::ZERO,
2491 token1_imaginary_reserves: U256::ZERO,
2492 }
2493 }
2494
2495 #[test]
2496 fn test_swap_out_verify_reserves_in_range() {
2497 let decimals: i64 = 6;
2498 let sync_time = SystemTime::now()
2499 .duration_since(UNIX_EPOCH)
2500 .unwrap()
2501 .as_secs() -
2502 10;
2503
2504 let mut col_reserves = new_verify_ratio_col_reserves_swap_out();
2505 let mut debt_reserves = new_verify_ratio_debt_reserves_swap_out();
2506
2507 let price = U256::from_str("1000001000000000000000000000").unwrap();
2509
2510 let (reserve_x_outside, reserve_y_outside) = calculate_reserves_outside_range(
2512 constant::B_I1E27,
2513 price,
2514 col_reserves.token0_real_reserves,
2515 col_reserves.token1_real_reserves,
2516 );
2517 col_reserves.token0_imaginary_reserves =
2518 U256::from(reserve_x_outside + I256::from(col_reserves.token0_real_reserves));
2519 col_reserves.token1_imaginary_reserves =
2520 U256::from(reserve_y_outside + I256::from(col_reserves.token1_real_reserves));
2521
2522 let (reserve_x_outside, reserve_y_outside) = calculate_reserves_outside_range(
2523 constant::B_I1E27,
2524 price,
2525 debt_reserves.token0_real_reserves,
2526 debt_reserves.token1_real_reserves,
2527 );
2528 debt_reserves.token0_imaginary_reserves =
2529 U256::from(reserve_x_outside + I256::from(debt_reserves.token0_real_reserves));
2530 debt_reserves.token1_imaginary_reserves =
2531 U256::from(reserve_y_outside + I256::from(debt_reserves.token1_real_reserves));
2532
2533 let swap_amount = U256::from(14_766u64) * U256::from(10u64).pow(U256::from(12));
2535
2536 let price = get_approx_center_price_out(
2537 swap_amount,
2538 false,
2539 &col_reserves,
2540 &new_debt_reserves_empty(),
2541 )
2542 .unwrap();
2543 let result = swap_out_adjusted(
2544 false,
2545 swap_amount,
2546 &mut col_reserves,
2547 &mut new_debt_reserves_empty(),
2548 decimals,
2549 decimals,
2550 &mut limits_wide(),
2551 price,
2552 sync_time,
2553 );
2554 assert!(result.is_err(), "FAIL: reserves ratio verification revert NOT hit for col reserves when swap amount 14_766");
2555
2556 let price = get_approx_center_price_out(
2557 swap_amount,
2558 false,
2559 &new_col_reserves_empty(),
2560 &debt_reserves,
2561 )
2562 .unwrap();
2563 let result = swap_out_adjusted(
2564 false,
2565 swap_amount,
2566 &mut new_col_reserves_empty(),
2567 &mut debt_reserves,
2568 decimals,
2569 decimals,
2570 &mut limits_wide(),
2571 price,
2572 sync_time,
2573 );
2574 assert!(result.is_err(), "FAIL: reserves ratio verification revert NOT hit for debt reserves when swap amount 14_766");
2575
2576 col_reserves = new_verify_ratio_col_reserves_swap_out();
2578 debt_reserves = new_verify_ratio_debt_reserves_swap_out();
2579
2580 let (reserve_x_outside, reserve_y_outside) = calculate_reserves_outside_range(
2581 constant::B_I1E27,
2582 price,
2583 col_reserves.token0_real_reserves,
2584 col_reserves.token1_real_reserves,
2585 );
2586 col_reserves.token0_imaginary_reserves =
2587 U256::from(reserve_x_outside + I256::from(col_reserves.token0_real_reserves));
2588 col_reserves.token1_imaginary_reserves =
2589 U256::from(reserve_y_outside + I256::from(col_reserves.token1_real_reserves));
2590
2591 let (reserve_x_outside, reserve_y_outside) = calculate_reserves_outside_range(
2592 constant::B_I1E27,
2593 price,
2594 debt_reserves.token0_real_reserves,
2595 debt_reserves.token1_real_reserves,
2596 );
2597 debt_reserves.token0_imaginary_reserves =
2598 U256::from(reserve_x_outside + I256::from(debt_reserves.token0_real_reserves));
2599 debt_reserves.token1_imaginary_reserves =
2600 U256::from(reserve_y_outside + I256::from(debt_reserves.token1_real_reserves));
2601
2602 let swap_amount = U256::from(14_762u64) * U256::from(10u64).pow(U256::from(12));
2604
2605 let price = get_approx_center_price_out(
2606 swap_amount,
2607 false,
2608 &col_reserves,
2609 &new_debt_reserves_empty(),
2610 )
2611 .unwrap();
2612 let result = swap_out_adjusted(
2613 false,
2614 swap_amount,
2615 &mut col_reserves,
2616 &mut new_debt_reserves_empty(),
2617 decimals,
2618 decimals,
2619 &mut limits_wide(),
2620 price,
2621 sync_time,
2622 );
2623 assert!(
2624 result.is_ok(),
2625 "FAIL: reserves ratio verification revert hit for col reserves when swap amount 14_762"
2626 );
2627
2628 let price = get_approx_center_price_out(
2629 swap_amount,
2630 false,
2631 &new_col_reserves_empty(),
2632 &debt_reserves,
2633 )
2634 .unwrap();
2635 let result = swap_out_adjusted(
2636 false,
2637 swap_amount,
2638 &mut new_col_reserves_empty(),
2639 &mut debt_reserves,
2640 decimals,
2641 decimals,
2642 &mut limits_wide(),
2643 price,
2644 sync_time,
2645 );
2646 assert!(result.is_ok(), "FAIL: reserves ratio verification revert hit for debt reserves when swap amount 14_762");
2647 }
2648
2649 fn hard_limit(l: u128) -> TokenLimit {
2666 TokenLimit {
2667 available: U256::from(l),
2668 expands_to: U256::from(l),
2669 expand_duration: U256::ZERO,
2670 }
2671 }
2672
2673 fn wsteth_eth_pool_23526115() -> (Token, Token, FluidV1) {
2674 let wsteth = Token::new(
2675 &Bytes::from_str("0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0").unwrap(),
2676 "wsteth",
2677 18,
2678 0,
2679 &[Some(20000)],
2680 Chain::Ethereum,
2681 100,
2682 );
2683 let eth = Token::new(
2684 &Bytes::from_str("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE").unwrap(),
2685 "ETH",
2686 18,
2687 0,
2688 &[Some(2000)],
2689 Chain::Ethereum,
2690 100,
2691 );
2692 let pool = FluidV1::new(
2693 &Bytes::from_str("0x0B1a513ee24972DAEf112bC777a5610d4325C9e7").unwrap(),
2694 &wsteth,
2695 ð,
2696 CollateralReserves {
2697 token0_real_reserves: U256::from(4431191840536456u128),
2698 token1_real_reserves: U256::from(13105569017021951u128),
2699 token0_imaginary_reserves: U256::from(20263646714209556492u128),
2700 token1_imaginary_reserves: U256::from(24624319733997222300u128),
2701 },
2702 DebtReserves {
2703 token0_real_reserves: U256::from(3958052320699256u128),
2704 token1_real_reserves: U256::from(11706224851989005u128),
2705 token0_imaginary_reserves: U256::from(18100000404581051720u128),
2706 token1_imaginary_reserves: U256::from(21995063545785045888u128),
2707 },
2708 DexLimits {
2709 borrowable_token0: hard_limit(4431191840536456767040),
2710 borrowable_token1: hard_limit(6552784508510975527319),
2711 withdrawable_token0: hard_limit(4819160955805377144139),
2712 withdrawable_token1: hard_limit(6126272539623278413525),
2713 },
2714 U256::from_str("1215727283480584508000000000").unwrap(),
2715 U256::from(68),
2716 1759795200,
2717 );
2718 (wsteth, eth, pool)
2719 }
2720
2721 #[test]
2722 fn test_spot_price() {
2723 let (wsteth, eth, pool) = wsteth_eth_pool_23526115();
2724 let exp_spot0 = 1.21519682; let exp_spot1 = 0.82291191; let spot0 = pool.spot_price(&wsteth, ð).unwrap();
2729 let spot1 = pool.spot_price(ð, &wsteth).unwrap();
2730
2731 let rel_err0 = (spot0 - exp_spot0).abs() / exp_spot0;
2732 let rel_err1 = (spot1 - exp_spot1).abs() / exp_spot1;
2733
2734 assert!(
2735 rel_err0 < 1e-4,
2736 "spot0 mismatch: got {spot0}, expected {exp_spot0}, relative error: {rel_err0}"
2737 );
2738 assert!(
2739 rel_err1 < 1e-4,
2740 "spot1 mismatch: got {spot1}, expected {exp_spot1}, relative error: {rel_err1}"
2741 );
2742 }
2743
2744 #[test]
2745 fn test_get_amount_out_zero2one() {
2746 let (wsteth, eth, pool) = wsteth_eth_pool_23526115();
2747 let amount_in = BigUint::from_str_radix("100000000000000", 10).unwrap();
2748 let exp_amount_out = BigUint::from_str_radix("121511421000000", 10).unwrap();
2750
2751 let res = pool
2752 .get_amount_out(amount_in, &wsteth, ð)
2753 .unwrap();
2754
2755 assert_eq!(res.amount, exp_amount_out);
2756 }
2757
2758 #[test]
2759 fn test_get_amount_out_one2zero() {
2760 let (wsteth, eth, pool) = wsteth_eth_pool_23526115();
2761 let amount_in = BigUint::from_str_radix("100000000000000", 10).unwrap();
2762 let exp_amount_out = BigUint::from_str_radix("82285598000000", 10).unwrap();
2764
2765 let res = pool
2766 .get_amount_out(amount_in, ð, &wsteth)
2767 .unwrap();
2768 assert_eq!(res.amount, exp_amount_out);
2769 }
2770
2771 #[test]
2772 fn get_limits_zero2one() {
2773 let (wsteth, eth, pool) = wsteth_eth_pool_23526115();
2774
2775 let (max_amount_in, _) = pool
2776 .get_limits(wsteth.address.clone(), eth.address.clone())
2777 .unwrap();
2778 let max_amount_onchain_test =
2779 BigUint::from_str_radix("10200000000000000000000", 10).unwrap();
2781
2782 let _ = pool
2783 .get_amount_out(max_amount_in.clone(), &wsteth, ð)
2784 .unwrap();
2785 assert!(max_amount_in < max_amount_onchain_test);
2786 }
2787
2788 #[test]
2789 fn get_limits_one2zero() {
2790 let (wsteth, eth, pool) = wsteth_eth_pool_23526115();
2791
2792 let (max_amount_in, _) = pool
2793 .get_limits(eth.address.clone(), wsteth.address.clone())
2794 .unwrap();
2795 let max_amount_onchain_test =
2796 BigUint::from_str_radix("10192694739404003000000", 10).unwrap();
2798
2799 let _ = pool
2800 .get_amount_out(max_amount_in.clone(), ð, &wsteth)
2801 .unwrap();
2802
2803 assert!(max_amount_in < max_amount_onchain_test);
2804 }
2805}