1use crate::{
2 error::{Result, TempoPrecompileError},
3 storage::Handler,
4 tip_fee_manager::{ITIPFeeAMM, TIPFeeAMMError, TIPFeeAMMEvent, TipFeeManager},
5 tip20::{ITIP20, TIP20Token, validate_usd_currency},
6};
7use alloy::{
8 primitives::{Address, B256, U256, keccak256, uint},
9 sol_types::SolValue,
10};
11use tempo_precompiles_macros::Storable;
12
13pub const M: U256 = uint!(9970_U256);
15pub const N: U256 = uint!(9985_U256);
17pub const SCALE: U256 = uint!(10000_U256);
19pub const MIN_LIQUIDITY: U256 = uint!(1000_U256);
21
22#[inline]
27pub fn compute_amount_out(amount_in: U256) -> Result<U256> {
28 amount_in
29 .checked_mul(M)
30 .map(|product| product / SCALE)
31 .ok_or(TempoPrecompileError::under_overflow())
32}
33
34#[derive(Debug, Clone, Default, Storable)]
36pub struct Pool {
37 pub reserve_user_token: u128,
39 pub reserve_validator_token: u128,
41}
42
43impl From<Pool> for ITIPFeeAMM::Pool {
44 fn from(value: Pool) -> Self {
45 Self {
46 reserveUserToken: value.reserve_user_token,
47 reserveValidatorToken: value.reserve_validator_token,
48 }
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Hash, Storable)]
54pub struct PoolKey {
55 pub user_token: Address,
57 pub validator_token: Address,
59}
60
61impl Pool {
62 pub fn decode_from_slot(slot_value: U256) -> Self {
64 use crate::storage::{LayoutCtx, Storable, packing::PackedSlot};
65
66 Self::load(&PackedSlot(slot_value), U256::ZERO, LayoutCtx::FULL)
68 .expect("unable to decode Pool from slot")
69 }
70}
71
72impl PoolKey {
73 pub fn new(user_token: Address, validator_token: Address) -> Self {
76 Self {
77 user_token,
78 validator_token,
79 }
80 }
81
82 pub fn get_id(&self) -> B256 {
85 keccak256((self.user_token, self.validator_token).abi_encode())
86 }
87}
88
89impl TipFeeManager {
90 pub fn pool_id(&self, user_token: Address, validator_token: Address) -> B256 {
93 PoolKey::new(user_token, validator_token).get_id()
94 }
95
96 pub fn get_pool(&self, call: ITIPFeeAMM::getPoolCall) -> Result<Pool> {
98 let pool_id = self.pool_id(call.userToken, call.validatorToken);
99 self.pools[pool_id].read()
100 }
101
102 pub fn check_sufficient_liquidity(&mut self, pool_id: B256, max_amount: U256) -> Result<u128> {
109 let amount_out_needed = compute_amount_out(max_amount)?;
110 let pool = self.pools[pool_id].read()?;
111
112 if amount_out_needed > U256::from(pool.reserve_validator_token) {
113 return Err(TIPFeeAMMError::insufficient_liquidity().into());
114 }
115
116 amount_out_needed
117 .try_into()
118 .map_err(|_| TempoPrecompileError::under_overflow())
119 }
120
121 #[inline]
123 pub fn reserve_pool_liquidity(&mut self, pool_id: B256, amount: u128) -> Result<()> {
124 self.pending_fee_swap_reservation[pool_id].t_write(amount)
125 }
126
127 pub fn rebalance_swap(
136 &mut self,
137 msg_sender: Address,
138 user_token: Address,
139 validator_token: Address,
140 amount_out: U256,
141 to: Address,
142 ) -> Result<U256> {
143 if amount_out.is_zero() {
144 return Err(TIPFeeAMMError::invalid_amount().into());
145 }
146
147 let pool_id = self.pool_id(user_token, validator_token);
148 let mut pool = self.pools[pool_id].read()?;
149
150 let amount_in = amount_out
153 .checked_mul(N)
154 .and_then(|product| product.checked_div(SCALE))
155 .and_then(|result| result.checked_add(U256::ONE))
156 .ok_or(TempoPrecompileError::under_overflow())?;
157
158 let amount_in: u128 = amount_in
159 .try_into()
160 .map_err(|_| TIPFeeAMMError::invalid_amount())?;
161 let amount_out: u128 = amount_out
162 .try_into()
163 .map_err(|_| TIPFeeAMMError::invalid_amount())?;
164
165 pool.reserve_validator_token = pool
166 .reserve_validator_token
167 .checked_add(amount_in)
168 .ok_or(TIPFeeAMMError::insufficient_reserves())?;
169
170 pool.reserve_user_token = pool
171 .reserve_user_token
172 .checked_sub(amount_out)
173 .ok_or(TIPFeeAMMError::invalid_amount())?;
174
175 if self.storage.spec().is_t1c() {
176 let reserved = self.pending_fee_swap_reservation[pool_id].t_read()?;
177 if pool.reserve_validator_token < reserved {
178 return Err(TIPFeeAMMError::insufficient_liquidity().into());
179 }
180 }
181
182 self.pools[pool_id].write(pool)?;
183
184 let amount_in = U256::from(amount_in);
185 let amount_out = U256::from(amount_out);
186 TIP20Token::from_address(validator_token)?.system_transfer_from(
187 msg_sender,
188 self.address,
189 amount_in,
190 )?;
191
192 TIP20Token::from_address(user_token)?.transfer(
193 self.address,
194 ITIP20::transferCall {
195 to,
196 amount: amount_out,
197 },
198 )?;
199
200 self.emit_event(TIPFeeAMMEvent::RebalanceSwap(ITIPFeeAMM::RebalanceSwap {
201 userToken: user_token,
202 validatorToken: validator_token,
203 swapper: msg_sender,
204 amountIn: amount_in,
205 amountOut: amount_out,
206 }))?;
207
208 Ok(amount_in)
209 }
210
211 pub fn mint(
230 &mut self,
231 msg_sender: Address,
232 user_token: Address,
233 validator_token: Address,
234 amount_validator_token: U256,
235 to: Address,
236 ) -> Result<U256> {
237 if user_token == validator_token {
238 return Err(TIPFeeAMMError::identical_addresses().into());
239 }
240
241 if amount_validator_token.is_zero() {
242 return Err(TIPFeeAMMError::invalid_amount().into());
243 }
244
245 validate_usd_currency(user_token)?;
247 validate_usd_currency(validator_token)?;
248
249 let pool_id = self.pool_id(user_token, validator_token);
250 let mut pool = self.pools[pool_id].read()?;
251 let mut total_supply = self.get_total_supply(pool_id)?;
252
253 let liquidity = if pool.reserve_user_token == 0 && pool.reserve_validator_token == 0 {
254 let half_amount = amount_validator_token
255 .checked_div(uint!(2_U256))
256 .ok_or(TempoPrecompileError::under_overflow())?;
257
258 if half_amount <= MIN_LIQUIDITY {
259 return Err(TIPFeeAMMError::insufficient_liquidity().into());
260 }
261
262 total_supply = total_supply
263 .checked_add(MIN_LIQUIDITY)
264 .ok_or(TempoPrecompileError::under_overflow())?;
265 self.set_total_supply(pool_id, total_supply)?;
266
267 half_amount
268 .checked_sub(MIN_LIQUIDITY)
269 .ok_or(TIPFeeAMMError::insufficient_liquidity())?
270 } else {
271 let product = N
274 .checked_mul(U256::from(pool.reserve_user_token))
275 .and_then(|product| product.checked_div(SCALE))
276 .ok_or(TIPFeeAMMError::invalid_swap_calculation())?;
277
278 let denom = U256::from(pool.reserve_validator_token)
279 .checked_add(product)
280 .ok_or(TIPFeeAMMError::invalid_amount())?;
281
282 if denom.is_zero() {
283 return Err(TIPFeeAMMError::division_by_zero().into());
284 }
285
286 amount_validator_token
287 .checked_mul(total_supply)
288 .and_then(|numerator| numerator.checked_div(denom))
289 .ok_or(TIPFeeAMMError::invalid_swap_calculation())?
290 };
291
292 if liquidity.is_zero() {
293 return Err(TIPFeeAMMError::insufficient_liquidity().into());
294 }
295
296 let _ = TIP20Token::from_address(validator_token)?.system_transfer_from(
298 msg_sender,
299 self.address,
300 amount_validator_token,
301 )?;
302
303 let validator_amount: u128 = amount_validator_token
305 .try_into()
306 .map_err(|_| TIPFeeAMMError::invalid_amount())?;
307
308 pool.reserve_validator_token = pool
309 .reserve_validator_token
310 .checked_add(validator_amount)
311 .ok_or(TIPFeeAMMError::invalid_amount())?;
312
313 self.pools[pool_id].write(pool)?;
314
315 self.set_total_supply(
317 pool_id,
318 total_supply
319 .checked_add(liquidity)
320 .ok_or(TempoPrecompileError::under_overflow())?,
321 )?;
322
323 let balance = self.get_liquidity_balances(pool_id, to)?;
324 self.set_liquidity_balances(
325 pool_id,
326 to,
327 balance
328 .checked_add(liquidity)
329 .ok_or(TempoPrecompileError::under_overflow())?,
330 )?;
331
332 self.emit_event(TIPFeeAMMEvent::Mint(ITIPFeeAMM::Mint {
334 sender: msg_sender,
335 to,
336 userToken: user_token,
337 validatorToken: validator_token,
338 amountValidatorToken: amount_validator_token,
339 liquidity,
340 }))?;
341
342 Ok(liquidity)
343 }
344
345 pub fn burn(
359 &mut self,
360 msg_sender: Address,
361 user_token: Address,
362 validator_token: Address,
363 liquidity: U256,
364 to: Address,
365 ) -> Result<(U256, U256)> {
366 if user_token == validator_token {
367 return Err(TIPFeeAMMError::identical_addresses().into());
368 }
369
370 if liquidity.is_zero() {
371 return Err(TIPFeeAMMError::invalid_amount().into());
372 }
373
374 validate_usd_currency(user_token)?;
376 validate_usd_currency(validator_token)?;
377
378 let pool_id = self.pool_id(user_token, validator_token);
379 let balance = self.get_liquidity_balances(pool_id, msg_sender)?;
381 if balance < liquidity {
382 return Err(TIPFeeAMMError::insufficient_liquidity().into());
383 }
384
385 let mut pool = self.pools[pool_id].read()?;
386 let (amount_user_token, amount_validator_token) =
388 self.calculate_burn_amounts(&pool, pool_id, liquidity)?;
389
390 let validator_amount: u128 = amount_validator_token
393 .try_into()
394 .map_err(|_| TIPFeeAMMError::invalid_amount())?;
395 let available_after_burn = pool
396 .reserve_validator_token
397 .checked_sub(validator_amount)
398 .ok_or(TIPFeeAMMError::insufficient_reserves())?;
399 if self.storage.spec().is_t1c() {
400 let reserved = self.pending_fee_swap_reservation[pool_id].t_read()?;
401 if available_after_burn < reserved {
402 return Err(TIPFeeAMMError::insufficient_liquidity().into());
403 }
404 }
405
406 self.set_liquidity_balances(
408 pool_id,
409 msg_sender,
410 balance
411 .checked_sub(liquidity)
412 .ok_or(TempoPrecompileError::under_overflow())?,
413 )?;
414 let total_supply = self.get_total_supply(pool_id)?;
415 self.set_total_supply(
416 pool_id,
417 total_supply
418 .checked_sub(liquidity)
419 .ok_or(TempoPrecompileError::under_overflow())?,
420 )?;
421
422 let user_amount: u128 = amount_user_token
424 .try_into()
425 .map_err(|_| TIPFeeAMMError::invalid_amount())?;
426 let validator_amount: u128 = amount_validator_token
427 .try_into()
428 .map_err(|_| TIPFeeAMMError::invalid_amount())?;
429
430 pool.reserve_user_token = pool
431 .reserve_user_token
432 .checked_sub(user_amount)
433 .ok_or(TIPFeeAMMError::insufficient_reserves())?;
434 pool.reserve_validator_token = pool
435 .reserve_validator_token
436 .checked_sub(validator_amount)
437 .ok_or(TIPFeeAMMError::insufficient_reserves())?;
438 self.pools[pool_id].write(pool)?;
439
440 let _ = TIP20Token::from_address(user_token)?.transfer(
442 self.address,
443 ITIP20::transferCall {
444 to,
445 amount: amount_user_token,
446 },
447 )?;
448
449 let _ = TIP20Token::from_address(validator_token)?.transfer(
450 self.address,
451 ITIP20::transferCall {
452 to,
453 amount: amount_validator_token,
454 },
455 )?;
456
457 self.emit_event(TIPFeeAMMEvent::Burn(ITIPFeeAMM::Burn {
459 sender: msg_sender,
460 userToken: user_token,
461 validatorToken: validator_token,
462 amountUserToken: amount_user_token,
463 amountValidatorToken: amount_validator_token,
464 liquidity,
465 to,
466 }))?;
467
468 Ok((amount_user_token, amount_validator_token))
469 }
470
471 fn calculate_burn_amounts(
473 &self,
474 pool: &Pool,
475 pool_id: B256,
476 liquidity: U256,
477 ) -> Result<(U256, U256)> {
478 let total_supply = self.get_total_supply(pool_id)?;
479 let amount_user_token = liquidity
480 .checked_mul(U256::from(pool.reserve_user_token))
481 .and_then(|product| product.checked_div(total_supply))
482 .ok_or(TempoPrecompileError::under_overflow())?;
483 let amount_validator_token = liquidity
484 .checked_mul(U256::from(pool.reserve_validator_token))
485 .and_then(|product| product.checked_div(total_supply))
486 .ok_or(TempoPrecompileError::under_overflow())?;
487
488 Ok((amount_user_token, amount_validator_token))
489 }
490
491 pub fn execute_fee_swap(
498 &mut self,
499 user_token: Address,
500 validator_token: Address,
501 amount_in: U256,
502 ) -> Result<U256> {
503 let pool_id = self.pool_id(user_token, validator_token);
504 let mut pool = self.pools[pool_id].read()?;
505
506 let amount_out = compute_amount_out(amount_in)?;
508
509 if amount_out > U256::from(pool.reserve_validator_token) {
511 return Err(TIPFeeAMMError::insufficient_liquidity().into());
512 }
513
514 let amount_in_u128: u128 = amount_in
516 .try_into()
517 .map_err(|_| TempoPrecompileError::under_overflow())?;
518 let amount_out_u128: u128 = amount_out
519 .try_into()
520 .map_err(|_| TempoPrecompileError::under_overflow())?;
521
522 pool.reserve_user_token = pool
523 .reserve_user_token
524 .checked_add(amount_in_u128)
525 .ok_or(TempoPrecompileError::under_overflow())?;
526 pool.reserve_validator_token = pool
527 .reserve_validator_token
528 .checked_sub(amount_out_u128)
529 .ok_or(TempoPrecompileError::under_overflow())?;
530
531 self.pools[pool_id].write(pool)?;
532
533 Ok(amount_out)
534 }
535
536 pub fn get_total_supply(&self, pool_id: B256) -> Result<U256> {
538 self.total_supply[pool_id].read()
539 }
540
541 fn set_total_supply(&mut self, pool_id: B256, total_supply: U256) -> Result<()> {
543 self.total_supply[pool_id].write(total_supply)
544 }
545
546 pub fn get_liquidity_balances(&self, pool_id: B256, user: Address) -> Result<U256> {
548 self.liquidity_balances[pool_id][user].read()
549 }
550
551 fn set_liquidity_balances(
553 &mut self,
554 pool_id: B256,
555 user: Address,
556 balance: U256,
557 ) -> Result<()> {
558 self.liquidity_balances[pool_id][user].write(balance)
559 }
560}
561
562#[cfg(test)]
563mod tests {
564 use alloy::primitives::Address;
565 use tempo_chainspec::hardfork::TempoHardfork;
566 use tempo_contracts::precompiles::TIP20Error;
567
568 use super::*;
569 use crate::{
570 error::TempoPrecompileError,
571 storage::{ContractStorage, StorageCtx, hashmap::HashMapStorageProvider},
572 test_util::TIP20Setup,
573 tip_fee_manager::TIPFeeAMMError,
574 };
575
576 fn sqrt(x: U256) -> U256 {
578 if x == U256::ZERO {
579 return U256::ZERO;
580 }
581 let mut z = (x + U256::ONE) / uint!(2_U256);
582 let mut y = x;
583 while z < y {
584 y = z;
585 z = (x / z + z) / uint!(2_U256);
586 }
587 y
588 }
589
590 fn setup_pool_with_liquidity(
592 amm: &mut TipFeeManager,
593 user_token: Address,
594 validator_token: Address,
595 user_amount: U256,
596 validator_amount: U256,
597 ) -> Result<B256> {
598 let pool_id = amm.pool_id(user_token, validator_token);
599 let pool = Pool {
600 reserve_user_token: user_amount.try_into().unwrap(),
601 reserve_validator_token: validator_amount.try_into().unwrap(),
602 };
603 amm.pools[pool_id].write(pool)?;
604 let liquidity = sqrt(user_amount * validator_amount);
605 amm.total_supply[pool_id].write(liquidity)?;
606 Ok(pool_id)
607 }
608
609 #[test]
610 fn test_mint_identical_addresses() -> eyre::Result<()> {
611 let mut storage = HashMapStorageProvider::new(1);
612 let admin = Address::random();
613 StorageCtx::enter(&mut storage, || {
614 let token = TIP20Setup::create("Test", "TST", admin).apply()?;
615 let mut amm = TipFeeManager::new();
616 let result = amm.mint(
617 admin,
618 token.address(),
619 token.address(),
620 U256::from(1000),
621 admin,
622 );
623 assert!(matches!(
624 result,
625 Err(TempoPrecompileError::TIPFeeAMMError(
626 TIPFeeAMMError::IdenticalAddresses(_)
627 ))
628 ));
629 Ok(())
630 })
631 }
632
633 #[test]
634 fn test_burn_identical_addresses() -> eyre::Result<()> {
635 let mut storage = HashMapStorageProvider::new(1);
636 let admin = Address::random();
637 StorageCtx::enter(&mut storage, || {
638 let token = TIP20Setup::create("Test", "TST", admin).apply()?;
639 let mut amm = TipFeeManager::new();
640 let result = amm.burn(
641 admin,
642 token.address(),
643 token.address(),
644 U256::from(1000),
645 admin,
646 );
647 assert!(matches!(
648 result,
649 Err(TempoPrecompileError::TIPFeeAMMError(
650 TIPFeeAMMError::IdenticalAddresses(_)
651 ))
652 ));
653 Ok(())
654 })
655 }
656
657 #[test]
658 fn test_rebalance_swap_insufficient_funds() -> eyre::Result<()> {
659 let mut storage = HashMapStorageProvider::new(1);
660 let admin = Address::random();
661 let to = Address::random();
662 StorageCtx::enter(&mut storage, || {
663 let user_token = TIP20Setup::create("UserToken", "UTK", admin).apply()?;
664 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin).apply()?;
665
666 let mut amm = TipFeeManager::new();
667 let amount = uint!(100000_U256) * uint!(10_U256).pow(U256::from(6));
668 setup_pool_with_liquidity(
669 &mut amm,
670 user_token.address(),
671 validator_token.address(),
672 amount,
673 amount,
674 )?;
675
676 let result = amm.rebalance_swap(
677 admin,
678 user_token.address(),
679 validator_token.address(),
680 amount + U256::ONE,
681 to,
682 );
683 assert!(matches!(
684 result,
685 Err(TempoPrecompileError::TIPFeeAMMError(
686 TIPFeeAMMError::InvalidAmount(_)
687 ))
688 ));
689 Ok(())
690 })
691 }
692
693 #[test]
694 fn test_mint_rejects_non_usd_user_token() -> eyre::Result<()> {
695 let mut storage = HashMapStorageProvider::new(1);
696 let admin = Address::random();
697 StorageCtx::enter(&mut storage, || {
698 let eur_token = TIP20Setup::create("EuroToken", "EUR", admin)
699 .currency("EUR")
700 .apply()?;
701 let usd_token = TIP20Setup::create("USDToken", "USD", admin).apply()?;
702 let mut amm = TipFeeManager::new();
703
704 let result = amm.mint(
705 admin,
706 eur_token.address(),
707 usd_token.address(),
708 U256::from(1000),
709 admin,
710 );
711 assert!(matches!(
712 result,
713 Err(TempoPrecompileError::TIP20(TIP20Error::InvalidCurrency(_)))
714 ));
715
716 let result = amm.mint(
717 admin,
718 usd_token.address(),
719 eur_token.address(),
720 U256::from(1000),
721 admin,
722 );
723 assert!(matches!(
724 result,
725 Err(TempoPrecompileError::TIP20(TIP20Error::InvalidCurrency(_)))
726 ));
727 Ok(())
728 })
729 }
730
731 #[test]
732 fn test_burn_rejects_non_usd_tokens() -> eyre::Result<()> {
733 let mut storage = HashMapStorageProvider::new(1);
734 let admin = Address::random();
735 StorageCtx::enter(&mut storage, || {
736 let eur_token = TIP20Setup::create("EuroToken", "EUR", admin)
737 .currency("EUR")
738 .apply()?;
739 let usd_token = TIP20Setup::create("USDToken", "USD", admin).apply()?;
740 let mut amm = TipFeeManager::new();
741
742 let result = amm.burn(
743 admin,
744 eur_token.address(),
745 usd_token.address(),
746 U256::from(1000),
747 admin,
748 );
749 assert!(matches!(
750 result,
751 Err(TempoPrecompileError::TIP20(TIP20Error::InvalidCurrency(_)))
752 ));
753
754 let result = amm.burn(
755 admin,
756 usd_token.address(),
757 eur_token.address(),
758 U256::from(1000),
759 admin,
760 );
761 assert!(matches!(
762 result,
763 Err(TempoPrecompileError::TIP20(TIP20Error::InvalidCurrency(_)))
764 ));
765 Ok(())
766 })
767 }
768
769 #[test]
770 fn test_mint_insufficient_amount() -> eyre::Result<()> {
771 let mut storage = HashMapStorageProvider::new(1);
772 let admin = Address::random();
773 StorageCtx::enter(&mut storage, || {
774 let user_token = TIP20Setup::create("UserToken", "UTK", admin).apply()?;
775 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin).apply()?;
776 let mut amm = TipFeeManager::new();
777
778 let insufficient = uint!(2000_U256);
780 let result = amm.mint(
781 admin,
782 user_token.address(),
783 validator_token.address(),
784 insufficient,
785 admin,
786 );
787 assert!(matches!(
788 result,
789 Err(TempoPrecompileError::TIPFeeAMMError(
790 TIPFeeAMMError::InsufficientLiquidity(_)
791 ))
792 ));
793 Ok(())
794 })
795 }
796
797 #[test]
798 fn test_add_liquidity() -> eyre::Result<()> {
799 let mut storage = HashMapStorageProvider::new(1);
800 let admin = Address::random();
801
802 StorageCtx::enter(&mut storage, || {
803 let mint_amount = uint!(10000000_U256);
804 let token1 = TIP20Setup::create("Token1", "TK1", admin)
805 .with_issuer(admin)
806 .with_mint(admin, mint_amount)
807 .apply()?
808 .address();
809 let token2 = TIP20Setup::create("Token2", "TK2", admin)
810 .with_issuer(admin)
811 .with_mint(admin, mint_amount)
812 .apply()?
813 .address();
814
815 let mut amm = TipFeeManager::new();
816 let amount = uint!(10000_U256);
817 let result = amm.mint(admin, token1, token2, amount, admin)?;
818 let expected_mean = amount / uint!(2_U256);
819 let expected_liquidity = expected_mean - MIN_LIQUIDITY;
820
821 assert_eq!(result, expected_liquidity,);
822
823 Ok(())
824 })
825 }
826
827 #[test]
828 fn test_calculate_burn_amounts() -> eyre::Result<()> {
829 let mut storage = HashMapStorageProvider::new(1);
830
831 StorageCtx::enter(&mut storage, || {
832 let mut amm = TipFeeManager::new();
833
834 let pool = Pool {
835 reserve_user_token: 1000,
836 reserve_validator_token: 1000,
837 };
838 let pool_id = B256::ZERO;
839 amm.set_total_supply(pool_id, uint!(1000000000000000_U256))?;
840
841 let liquidity = uint!(1_U256);
842 let result = amm.calculate_burn_amounts(&pool, pool_id, liquidity);
843
844 assert!(result.is_ok());
845 let (amount_user, amount_validator) = result?;
846 assert_eq!(amount_user, U256::ZERO);
847 assert_eq!(amount_validator, U256::ZERO);
848
849 Ok(())
850 })
851 }
852
853 #[test]
855 fn test_execute_fee_swap_immediate() -> eyre::Result<()> {
856 let mut storage = HashMapStorageProvider::new(1);
857 let admin = Address::random();
858
859 StorageCtx::enter(&mut storage, || {
860 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
861 .apply()?
862 .address();
863 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
864 .apply()?
865 .address();
866
867 let mut amm = TipFeeManager::new();
868
869 let liquidity_amount = uint!(1000_U256);
871 let pool_id = setup_pool_with_liquidity(
872 &mut amm,
873 user_token,
874 validator_token,
875 liquidity_amount,
876 liquidity_amount,
877 )?;
878
879 let amount_in = uint!(100_U256);
881 let expected_out = (amount_in * M) / SCALE; let amount_out = amm.execute_fee_swap(user_token, validator_token, amount_in)?;
884
885 assert_eq!(amount_out, expected_out);
886
887 let pool = amm.pools[pool_id].read()?;
889 assert_eq!(
890 U256::from(pool.reserve_user_token),
891 liquidity_amount + amount_in
892 );
893 assert_eq!(
894 U256::from(pool.reserve_validator_token),
895 liquidity_amount - expected_out
896 );
897
898 Ok(())
899 })
900 }
901
902 #[test]
904 fn test_execute_fee_swap_insufficient_liquidity() -> eyre::Result<()> {
905 let mut storage = HashMapStorageProvider::new(1);
906 let admin = Address::random();
907
908 StorageCtx::enter(&mut storage, || {
909 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
910 .apply()?
911 .address();
912 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
913 .apply()?
914 .address();
915
916 let mut amm = TipFeeManager::new();
917
918 let small_liquidity = uint!(100_U256);
920 setup_pool_with_liquidity(
921 &mut amm,
922 user_token,
923 validator_token,
924 small_liquidity,
925 small_liquidity,
926 )?;
927
928 let too_large_amount = uint!(200_U256);
930
931 let result = amm.execute_fee_swap(user_token, validator_token, too_large_amount);
932
933 assert!(matches!(
934 result,
935 Err(TempoPrecompileError::TIPFeeAMMError(
936 TIPFeeAMMError::InsufficientLiquidity(_)
937 ))
938 ));
939
940 Ok(())
941 })
942 }
943
944 #[test]
946 fn test_fee_swap_rounding_consistency() -> eyre::Result<()> {
947 let mut storage = HashMapStorageProvider::new(1);
948 let admin = Address::random();
949
950 StorageCtx::enter(&mut storage, || {
951 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
952 .apply()?
953 .address();
954 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
955 .apply()?
956 .address();
957
958 let mut amm = TipFeeManager::new();
959 let liquidity = uint!(100000_U256) * uint!(10_U256).pow(U256::from(6));
960 let pool_id = setup_pool_with_liquidity(
961 &mut amm,
962 user_token,
963 validator_token,
964 liquidity,
965 liquidity,
966 )?;
967
968 let amount_in = uint!(10000_U256) * uint!(10_U256).pow(U256::from(6));
969 let expected_out = (amount_in * M) / SCALE;
970
971 let actual_out = amm.execute_fee_swap(user_token, validator_token, amount_in)?;
972 assert_eq!(actual_out, expected_out, "Output should match expected");
973
974 let pool = amm.pools[pool_id].read()?;
975 assert_eq!(
976 U256::from(pool.reserve_user_token),
977 liquidity + amount_in,
978 "User reserve should increase"
979 );
980 assert_eq!(
981 U256::from(pool.reserve_validator_token),
982 liquidity - actual_out,
983 "Validator reserve should decrease"
984 );
985
986 Ok(())
987 })
988 }
989
990 #[test]
992 fn test_multiple_consecutive_fee_swaps() -> eyre::Result<()> {
993 let mut storage = HashMapStorageProvider::new(1);
994 let admin = Address::random();
995
996 StorageCtx::enter(&mut storage, || {
997 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
998 .apply()?
999 .address();
1000 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
1001 .apply()?
1002 .address();
1003
1004 let mut amm = TipFeeManager::new();
1005 let initial = uint!(100000_U256) * uint!(10_U256).pow(U256::from(6));
1006 let pool_id =
1007 setup_pool_with_liquidity(&mut amm, user_token, validator_token, initial, initial)?;
1008
1009 let swap1 = uint!(1000_U256) * uint!(10_U256).pow(U256::from(6));
1010 let swap2 = uint!(2000_U256) * uint!(10_U256).pow(U256::from(6));
1011 let swap3 = uint!(3000_U256) * uint!(10_U256).pow(U256::from(6));
1012
1013 let out1 = amm.execute_fee_swap(user_token, validator_token, swap1)?;
1014 let out2 = amm.execute_fee_swap(user_token, validator_token, swap2)?;
1015 let out3 = amm.execute_fee_swap(user_token, validator_token, swap3)?;
1016
1017 let total_in = swap1 + swap2 + swap3;
1018 let total_out = out1 + out2 + out3;
1019
1020 assert_eq!(out1, (swap1 * M) / SCALE);
1022 assert_eq!(out2, (swap2 * M) / SCALE);
1023 assert_eq!(out3, (swap3 * M) / SCALE);
1024
1025 let pool = amm.pools[pool_id].read()?;
1026 assert_eq!(U256::from(pool.reserve_user_token), initial + total_in);
1027 assert_eq!(
1028 U256::from(pool.reserve_validator_token),
1029 initial - total_out
1030 );
1031
1032 Ok(())
1033 })
1034 }
1035
1036 #[test]
1038 fn test_check_sufficient_liquidity_boundary() -> eyre::Result<()> {
1039 let mut storage = HashMapStorageProvider::new(1);
1040 let admin = Address::random();
1041
1042 StorageCtx::enter(&mut storage, || {
1043 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
1044 .apply()?
1045 .address();
1046 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
1047 .apply()?
1048 .address();
1049
1050 let mut amm = TipFeeManager::new();
1051 let liquidity = uint!(100_U256) * uint!(10_U256).pow(U256::from(6));
1052 let pool_id = setup_pool_with_liquidity(
1053 &mut amm,
1054 user_token,
1055 validator_token,
1056 liquidity,
1057 liquidity,
1058 )?;
1059
1060 let ok_amount = uint!(100_U256) * uint!(10_U256).pow(U256::from(6));
1062 assert!(amm.check_sufficient_liquidity(pool_id, ok_amount).is_ok());
1063
1064 let too_much = uint!(101_U256) * uint!(10_U256).pow(U256::from(6));
1066 assert!(amm.check_sufficient_liquidity(pool_id, too_much).is_err());
1067
1068 Ok(())
1069 })
1070 }
1071
1072 #[test]
1074 fn test_burn_zero_liquidity() -> eyre::Result<()> {
1075 let mut storage = HashMapStorageProvider::new(1);
1076 let admin = Address::random();
1077
1078 StorageCtx::enter(&mut storage, || {
1079 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
1080 .apply()?
1081 .address();
1082 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
1083 .apply()?
1084 .address();
1085
1086 let mut amm = TipFeeManager::new();
1087
1088 let result = amm.burn(admin, user_token, validator_token, U256::ZERO, admin);
1089
1090 assert!(matches!(
1091 result,
1092 Err(TempoPrecompileError::TIPFeeAMMError(
1093 TIPFeeAMMError::InvalidAmount(_)
1094 ))
1095 ));
1096
1097 Ok(())
1098 })
1099 }
1100
1101 #[test]
1103 fn test_mint_zero_amount_validator_token() -> eyre::Result<()> {
1104 let mut storage = HashMapStorageProvider::new(1);
1105 let admin = Address::random();
1106
1107 StorageCtx::enter(&mut storage, || {
1108 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
1109 .apply()?
1110 .address();
1111 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
1112 .apply()?
1113 .address();
1114
1115 let mut amm = TipFeeManager::new();
1116
1117 let result = amm.mint(admin, user_token, validator_token, U256::ZERO, admin);
1118
1119 assert!(matches!(
1120 result,
1121 Err(TempoPrecompileError::TIPFeeAMMError(
1122 TIPFeeAMMError::InvalidAmount(_)
1123 ))
1124 ));
1125
1126 Ok(())
1127 })
1128 }
1129
1130 #[test]
1131 fn test_rebalance_swap() -> eyre::Result<()> {
1132 let mut storage = HashMapStorageProvider::new(1);
1133 let admin = Address::random();
1134 let recipient = Address::random();
1135
1136 StorageCtx::enter(&mut storage, || {
1137 let mint_amount = uint!(10000000_U256);
1138 let mut amm = TipFeeManager::new();
1139 let amm_address = amm.address;
1140
1141 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
1142 .with_issuer(admin)
1143 .with_mint(admin, mint_amount)
1144 .with_mint(amm_address, mint_amount)
1145 .apply()?
1146 .address();
1147 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
1148 .with_issuer(admin)
1149 .with_mint(admin, mint_amount)
1150 .apply()?
1151 .address();
1152
1153 let liquidity = uint!(100000_U256);
1154 let pool_id = setup_pool_with_liquidity(
1155 &mut amm,
1156 user_token,
1157 validator_token,
1158 liquidity,
1159 liquidity,
1160 )?;
1161
1162 let amount_out = uint!(1000_U256);
1163 let expected_in = (amount_out * N) / SCALE + U256::ONE;
1164
1165 let amount_in =
1166 amm.rebalance_swap(admin, user_token, validator_token, amount_out, recipient)?;
1167
1168 assert_eq!(amount_in, expected_in);
1169
1170 let pool = amm.pools[pool_id].read()?;
1171 assert_eq!(U256::from(pool.reserve_user_token), liquidity - amount_out);
1172 assert_eq!(
1173 U256::from(pool.reserve_validator_token),
1174 liquidity + amount_in
1175 );
1176
1177 Ok(())
1178 })
1179 }
1180
1181 #[test]
1182 fn test_mint_subsequent_deposit() -> eyre::Result<()> {
1183 let mut storage = HashMapStorageProvider::new(1);
1184 let admin = Address::random();
1185 let second_user = Address::random();
1186
1187 StorageCtx::enter(&mut storage, || {
1188 let mint_amount = uint!(100000000_U256);
1189 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
1190 .with_issuer(admin)
1191 .with_mint(admin, mint_amount)
1192 .with_mint(second_user, mint_amount)
1193 .apply()?
1194 .address();
1195 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
1196 .with_issuer(admin)
1197 .with_mint(admin, mint_amount)
1198 .with_mint(second_user, mint_amount)
1199 .apply()?
1200 .address();
1201
1202 let mut amm = TipFeeManager::new();
1203
1204 let initial_amount = uint!(100000_U256);
1205 let first_liquidity =
1206 amm.mint(admin, user_token, validator_token, initial_amount, admin)?;
1207
1208 let expected_first_liquidity = initial_amount / uint!(2_U256) - MIN_LIQUIDITY;
1209 assert_eq!(first_liquidity, expected_first_liquidity);
1210
1211 let pool_id = amm.pool_id(user_token, validator_token);
1212 let total_supply_after_first = amm.get_total_supply(pool_id)?;
1213 assert_eq!(total_supply_after_first, first_liquidity + MIN_LIQUIDITY);
1214
1215 let pool_after_first = amm.pools[pool_id].read()?;
1216 let reserve_val = U256::from(pool_after_first.reserve_validator_token);
1217
1218 let second_amount = uint!(50000_U256);
1219 let second_liquidity = amm.mint(
1220 second_user,
1221 user_token,
1222 validator_token,
1223 second_amount,
1224 second_user,
1225 )?;
1226
1227 let expected_second_liquidity = second_amount * total_supply_after_first / reserve_val;
1228 assert_eq!(second_liquidity, expected_second_liquidity);
1229
1230 let total_supply_after_second = amm.get_total_supply(pool_id)?;
1231 assert_eq!(
1232 total_supply_after_second,
1233 total_supply_after_first + second_liquidity
1234 );
1235
1236 let admin_balance = amm.get_liquidity_balances(pool_id, admin)?;
1237 let second_user_balance = amm.get_liquidity_balances(pool_id, second_user)?;
1238 assert_eq!(admin_balance, first_liquidity);
1239 assert_eq!(second_user_balance, second_liquidity);
1240
1241 Ok(())
1242 })
1243 }
1244
1245 #[test]
1246 fn test_burn() -> eyre::Result<()> {
1247 let mut storage = HashMapStorageProvider::new(1);
1248 let admin = Address::random();
1249 let recipient = Address::random();
1250
1251 StorageCtx::enter(&mut storage, || {
1252 let mint_amount = uint!(100000000_U256);
1253 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
1254 .with_issuer(admin)
1255 .with_mint(admin, mint_amount)
1256 .apply()?
1257 .address();
1258 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
1259 .with_issuer(admin)
1260 .with_mint(admin, mint_amount)
1261 .apply()?
1262 .address();
1263
1264 let mut amm = TipFeeManager::new();
1265
1266 let deposit_amount = uint!(100000_U256);
1267 let liquidity = amm.mint(admin, user_token, validator_token, deposit_amount, admin)?;
1268
1269 let expected_liquidity = deposit_amount / uint!(2_U256) - MIN_LIQUIDITY;
1270 assert_eq!(liquidity, expected_liquidity);
1271
1272 let pool_id = amm.pool_id(user_token, validator_token);
1273 let pool_before = amm.pools[pool_id].read()?;
1274 let total_supply_before = amm.get_total_supply(pool_id)?;
1275
1276 let burn_amount = liquidity / uint!(2_U256);
1277 let (amount_user, amount_validator) =
1278 amm.burn(admin, user_token, validator_token, burn_amount, recipient)?;
1279
1280 let expected_user =
1281 burn_amount * U256::from(pool_before.reserve_user_token) / total_supply_before;
1282 let expected_validator =
1283 burn_amount * U256::from(pool_before.reserve_validator_token) / total_supply_before;
1284 assert_eq!(amount_user, expected_user);
1285 assert_eq!(amount_validator, expected_validator);
1286
1287 let pool_after = amm.pools[pool_id].read()?;
1288 let total_supply_after = amm.get_total_supply(pool_id)?;
1289
1290 assert_eq!(total_supply_after, total_supply_before - burn_amount);
1291
1292 let admin_balance = amm.get_liquidity_balances(pool_id, admin)?;
1293 assert_eq!(admin_balance, liquidity - burn_amount);
1294
1295 assert_eq!(
1296 U256::from(pool_after.reserve_user_token),
1297 U256::from(pool_before.reserve_user_token) - amount_user
1298 );
1299 assert_eq!(
1300 U256::from(pool_after.reserve_validator_token),
1301 U256::from(pool_before.reserve_validator_token) - amount_validator
1302 );
1303
1304 Ok(())
1305 })
1306 }
1307
1308 #[test]
1309 fn test_burn_insufficient_balance() -> eyre::Result<()> {
1310 let mut storage = HashMapStorageProvider::new(1);
1311 let admin = Address::random();
1312 let other_user = Address::random();
1313
1314 StorageCtx::enter(&mut storage, || {
1315 let mint_amount = uint!(100000000_U256);
1316 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
1317 .with_issuer(admin)
1318 .with_mint(admin, mint_amount)
1319 .apply()?
1320 .address();
1321 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
1322 .with_issuer(admin)
1323 .with_mint(admin, mint_amount)
1324 .apply()?
1325 .address();
1326
1327 let mut amm = TipFeeManager::new();
1328
1329 let deposit_amount = uint!(100000_U256);
1330 let liquidity = amm.mint(admin, user_token, validator_token, deposit_amount, admin)?;
1331
1332 let result = amm.burn(
1333 other_user,
1334 user_token,
1335 validator_token,
1336 liquidity,
1337 other_user,
1338 );
1339
1340 assert!(matches!(
1341 result,
1342 Err(TempoPrecompileError::TIPFeeAMMError(
1343 TIPFeeAMMError::InsufficientLiquidity(_)
1344 ))
1345 ));
1346
1347 Ok(())
1348 })
1349 }
1350
1351 #[test]
1353 fn test_rebalance_swap_zero_amount_out() -> eyre::Result<()> {
1354 let mut storage = HashMapStorageProvider::new(1);
1355 let admin = Address::random();
1356 let to = Address::random();
1357
1358 StorageCtx::enter(&mut storage, || {
1359 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
1360 .apply()?
1361 .address();
1362 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
1363 .apply()?
1364 .address();
1365
1366 let mut amm = TipFeeManager::new();
1367
1368 let result = amm.rebalance_swap(admin, user_token, validator_token, U256::ZERO, to);
1369
1370 assert!(matches!(
1371 result,
1372 Err(TempoPrecompileError::TIPFeeAMMError(
1373 TIPFeeAMMError::InvalidAmount(_)
1374 ))
1375 ));
1376
1377 Ok(())
1378 })
1379 }
1380
1381 #[test]
1382 fn test_t1c_reserve_pool_liquidity() -> eyre::Result<()> {
1383 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T1C);
1384 let admin = Address::random();
1385
1386 StorageCtx::enter(&mut storage, || {
1387 let mint_amount = uint!(10000000_U256);
1388 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
1389 .with_issuer(admin)
1390 .with_mint(admin, mint_amount)
1391 .apply()?
1392 .address();
1393 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
1394 .with_issuer(admin)
1395 .with_mint(admin, mint_amount)
1396 .apply()?
1397 .address();
1398
1399 let mut amm = TipFeeManager::new();
1400 let liquidity = uint!(100000_U256);
1401 let pool_id = setup_pool_with_liquidity(
1402 &mut amm,
1403 user_token,
1404 validator_token,
1405 liquidity,
1406 liquidity,
1407 )?;
1408
1409 let max_amount = uint!(10000_U256);
1410 let amount_out = amm.check_sufficient_liquidity(pool_id, max_amount)?;
1411 amm.reserve_pool_liquidity(pool_id, amount_out)?;
1412
1413 let reserved = amm.pending_fee_swap_reservation[pool_id].t_read()?;
1414 let expected_reserved: u128 = compute_amount_out(max_amount)?.try_into().unwrap();
1415 assert_eq!(reserved, expected_reserved);
1416
1417 Ok(())
1418 })
1419 }
1420
1421 #[test]
1422 fn test_t1c_burn_respects_reservation() -> eyre::Result<()> {
1423 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T1C);
1424 let admin = Address::random();
1425 let recipient = Address::random();
1426
1427 StorageCtx::enter(&mut storage, || {
1428 let mint_amount = uint!(100000000_U256);
1429 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
1430 .with_issuer(admin)
1431 .with_mint(admin, mint_amount)
1432 .apply()?
1433 .address();
1434 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
1435 .with_issuer(admin)
1436 .with_mint(admin, mint_amount)
1437 .apply()?
1438 .address();
1439
1440 let mut amm = TipFeeManager::new();
1441
1442 let deposit_amount = uint!(100000_U256);
1443 let liquidity = amm.mint(admin, user_token, validator_token, deposit_amount, admin)?;
1444
1445 let pool_id = amm.pool_id(user_token, validator_token);
1446 let pool = amm.pools[pool_id].read()?;
1447
1448 let reserve_amount = U256::from(pool.reserve_validator_token) - uint!(100_U256);
1450 let amount_out = amm.check_sufficient_liquidity(pool_id, reserve_amount)?;
1451 amm.reserve_pool_liquidity(pool_id, amount_out)?;
1452
1453 let result = amm.burn(admin, user_token, validator_token, liquidity, recipient);
1454 assert!(matches!(
1455 result,
1456 Err(TempoPrecompileError::TIPFeeAMMError(
1457 TIPFeeAMMError::InsufficientLiquidity(_)
1458 ))
1459 ));
1460
1461 Ok(())
1462 })
1463 }
1464
1465 #[test]
1466 fn test_t1c_partial_burn_with_reservation() -> eyre::Result<()> {
1467 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T1C);
1468 let admin = Address::random();
1469 let recipient = Address::random();
1470
1471 StorageCtx::enter(&mut storage, || {
1472 let mint_amount = uint!(100000000_U256);
1473 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
1474 .with_issuer(admin)
1475 .with_mint(admin, mint_amount)
1476 .apply()?
1477 .address();
1478 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
1479 .with_issuer(admin)
1480 .with_mint(admin, mint_amount)
1481 .apply()?
1482 .address();
1483
1484 let mut amm = TipFeeManager::new();
1485
1486 let deposit_amount = uint!(100000_U256);
1487 let liquidity = amm.mint(admin, user_token, validator_token, deposit_amount, admin)?;
1488
1489 let pool_id = amm.pool_id(user_token, validator_token);
1490 let small_reserve = uint!(1000_U256);
1491 let amount_out = amm.check_sufficient_liquidity(pool_id, small_reserve)?;
1492 amm.reserve_pool_liquidity(pool_id, amount_out)?;
1493
1494 let small_burn = liquidity / uint!(10_U256);
1495 let result = amm.burn(admin, user_token, validator_token, small_burn, recipient);
1496
1497 assert!(result.is_ok());
1498
1499 Ok(())
1500 })
1501 }
1502
1503 #[test]
1504 fn test_t1c_rebalance_swap_respects_reservation() -> eyre::Result<()> {
1505 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T1C);
1506 let admin = Address::random();
1507 let to = Address::random();
1508
1509 StorageCtx::enter(&mut storage, || {
1510 let mint_amount = uint!(100000000_U256);
1511 let mut amm = TipFeeManager::new();
1512 let amm_address = amm.address;
1513 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
1514 .with_issuer(admin)
1515 .with_mint(admin, mint_amount)
1516 .with_mint(amm_address, mint_amount)
1517 .apply()?
1518 .address();
1519 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
1520 .with_issuer(admin)
1521 .with_mint(admin, mint_amount)
1522 .apply()?
1523 .address();
1524
1525 let liq = uint!(100000_U256);
1526 let pool_id =
1527 setup_pool_with_liquidity(&mut amm, user_token, validator_token, liq, liq)?;
1528
1529 let amount_out = amm.check_sufficient_liquidity(pool_id, uint!(50000_U256))?;
1530 amm.reserve_pool_liquidity(pool_id, amount_out)?;
1531
1532 amm.rebalance_swap(admin, user_token, validator_token, uint!(5000_U256), to)?;
1533 let pool = amm.pools[pool_id].read()?;
1534 let reserved = amm.pending_fee_swap_reservation[pool_id].t_read()?;
1535 assert!(pool.reserve_validator_token >= reserved);
1536
1537 Ok(())
1538 })
1539 }
1540
1541 #[test]
1542 fn test_pre_t1c_rebalance_swap_skips_reservation() -> eyre::Result<()> {
1543 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T1B);
1544 let admin = Address::random();
1545 let to = Address::random();
1546
1547 StorageCtx::enter(&mut storage, || {
1548 let mint_amount = uint!(100000000_U256);
1549 let mut amm = TipFeeManager::new();
1550 let amm_address = amm.address;
1551 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
1552 .with_issuer(admin)
1553 .with_mint(admin, mint_amount)
1554 .with_mint(amm_address, mint_amount)
1555 .apply()?
1556 .address();
1557 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
1558 .with_issuer(admin)
1559 .with_mint(admin, mint_amount)
1560 .apply()?
1561 .address();
1562
1563 let liq = uint!(100000_U256);
1564 let pool_id =
1565 setup_pool_with_liquidity(&mut amm, user_token, validator_token, liq, liq)?;
1566 amm.check_sufficient_liquidity(pool_id, uint!(90000_U256))?;
1567 assert!(
1568 amm.rebalance_swap(admin, user_token, validator_token, uint!(5000_U256), to)
1569 .is_ok()
1570 );
1571
1572 Ok(())
1573 })
1574 }
1575
1576 #[test]
1577 fn test_pre_t1c_no_reservation() -> eyre::Result<()> {
1578 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T1B);
1579 let admin = Address::random();
1580 let recipient = Address::random();
1581
1582 StorageCtx::enter(&mut storage, || {
1583 let mint_amount = uint!(100000000_U256);
1584 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
1585 .with_issuer(admin)
1586 .with_mint(admin, mint_amount)
1587 .apply()?
1588 .address();
1589 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
1590 .with_issuer(admin)
1591 .with_mint(admin, mint_amount)
1592 .apply()?
1593 .address();
1594
1595 let mut amm = TipFeeManager::new();
1596
1597 let deposit_amount = uint!(100000_U256);
1598 let liquidity = amm.mint(admin, user_token, validator_token, deposit_amount, admin)?;
1599
1600 let pool_id = amm.pool_id(user_token, validator_token);
1601 let pool = amm.pools[pool_id].read()?;
1602 let reserve_amount = U256::from(pool.reserve_validator_token) - uint!(100_U256);
1603 amm.check_sufficient_liquidity(pool_id, reserve_amount)?;
1604
1605 let result = amm.burn(admin, user_token, validator_token, liquidity, recipient);
1606 assert!(result.is_ok());
1607
1608 Ok(())
1609 })
1610 }
1611}