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