@@ -10,8 +10,9 @@ use margin_trading::{
1010 margin_registry::{Self , MarginRegistry , MarginAdminCap , MaintainerCap , MarginPoolCap },
1111 protocol_config,
1212 test_constants::{Self , USDC , USDT },
13- test_helpers::{Self , mint_coin}
13+ test_helpers::{Self , mint_coin, advance_time }
1414};
15+ use std::unit_test::assert_eq;
1516use sui::{
1617 clock::Clock ,
1718 coin::Coin ,
@@ -646,3 +647,237 @@ fun test_withdraw_exceeds_available_liquidity() {
646647 test::return_shared (pool);
647648 cleanup_test (registry, admin_cap, maintainer_cap, clock, scenario);
648649}
650+
651+ #[test]
652+ fun test_liquidation_exact_amount () {
653+ let (mut scenario, clock, admin_cap, maintainer_cap, pool_id) = setup_test ();
654+ scenario.next_tx (test_constants::admin ());
655+ let mut pool = scenario.take_shared_by_id <MarginPool <USDC >>(pool_id);
656+ let registry = scenario.take_shared <MarginRegistry >();
657+
658+ scenario.next_tx (test_constants::user1 ());
659+ let supply_coin = mint_coin <USDC >(1000 * test_constants::usdc_multiplier (), scenario.ctx ());
660+ pool.supply (®istry, supply_coin, option::none (), &clock, scenario.ctx ());
661+
662+ scenario.next_tx (test_constants::user2 ());
663+ let (borrowed_coin, _, shares) = pool.borrow (
664+ 500 * test_constants::usdc_multiplier (),
665+ &clock,
666+ scenario.ctx (),
667+ );
668+ destroy (borrowed_coin);
669+
670+ let exact_amount = pool.borrow_shares_to_amount (shares, &clock);
671+ let liquidation_coin = mint_coin <USDC >(exact_amount, scenario.ctx ());
672+ let (amount, reward, default) = pool.repay_liquidation (shares, liquidation_coin, &clock);
673+
674+ assert ! (amount == exact_amount);
675+ assert ! (reward == 0 );
676+ assert ! (default == 0 );
677+
678+ test::return_shared (pool);
679+ cleanup_test (registry, admin_cap, maintainer_cap, clock, scenario);
680+ }
681+
682+ #[test]
683+ fun test_liquidation_zero_shares () {
684+ let (mut scenario, clock, admin_cap, maintainer_cap, pool_id) = setup_test ();
685+ scenario.next_tx (test_constants::admin ());
686+ let mut pool = scenario.take_shared_by_id <MarginPool <USDC >>(pool_id);
687+ let registry = scenario.take_shared <MarginRegistry >();
688+
689+ scenario.next_tx (test_constants::user1 ());
690+ let supply_coin = mint_coin <USDC >(1000 * test_constants::usdc_multiplier (), scenario.ctx ());
691+ pool.supply (®istry, supply_coin, option::none (), &clock, scenario.ctx ());
692+
693+ let liquidation_coin = mint_coin <USDC >(100 * test_constants::usdc_multiplier (), scenario.ctx ());
694+ let (amount, reward, default) = pool.repay_liquidation (0 , liquidation_coin, &clock);
695+
696+ assert ! (amount == 0 );
697+ assert ! (reward == 100 * test_constants::usdc_multiplier ()); // all reward
698+ assert ! (default == 0 );
699+
700+ test::return_shared (pool);
701+ cleanup_test (registry, admin_cap, maintainer_cap, clock, scenario);
702+ }
703+
704+ #[test]
705+ fun test_supply_withdrawal_with_interest () {
706+ let (mut scenario, mut clock, admin_cap, maintainer_cap, pool_id) = setup_test ();
707+ scenario.next_tx (test_constants::admin ());
708+ let mut pool = scenario.take_shared_by_id <MarginPool <USDC >>(pool_id);
709+ let registry = scenario.take_shared <MarginRegistry >();
710+
711+ // Two users supply
712+ scenario.next_tx (test_constants::user1 ());
713+ let supply1 = 1000 * test_constants::usdc_multiplier ();
714+ let coin1 = mint_coin <USDC >(supply1, scenario.ctx ());
715+ pool.supply (®istry, coin1, option::none (), &clock, scenario.ctx ());
716+
717+ scenario.next_tx (test_constants::user2 ());
718+ let supply2 = 500 * test_constants::usdc_multiplier ();
719+ let coin2 = mint_coin <USDC >(supply2, scenario.ctx ());
720+ pool.supply (®istry, coin2, option::none (), &clock, scenario.ctx ());
721+
722+ scenario.next_tx (test_constants::liquidator ());
723+ let (borrowed_coin, _, _) = pool.borrow (
724+ 750 * test_constants::usdc_multiplier (),
725+ &clock,
726+ scenario.ctx (),
727+ );
728+ destroy (borrowed_coin);
729+
730+ advance_time (&mut clock, margin_constants::year_ms ());
731+
732+ // User1 withdraws 20% of initial deposit
733+ scenario.next_tx (test_constants::user1 ());
734+ let withdrawn1 = pool.withdraw (
735+ ®istry,
736+ option::some (200 * test_constants::usdc_multiplier ()),
737+ &clock,
738+ scenario.ctx (),
739+ );
740+
741+ assert_eq ! (withdrawn1.value (), 200 * test_constants::usdc_multiplier ());
742+ destroy (withdrawn1);
743+
744+ // User2 tries to withdraw all
745+ scenario.next_tx (test_constants::user2 ());
746+ let withdrawn2 = pool.withdraw (
747+ ®istry,
748+ option::none (),
749+ &clock,
750+ scenario.ctx (),
751+ );
752+
753+ assert ! (withdrawn2.value () > supply2);
754+ destroy (withdrawn2);
755+
756+ test::return_shared (pool);
757+ cleanup_test (registry, admin_cap, maintainer_cap, clock, scenario);
758+ }
759+
760+ #[test]
761+ fun test_partial_liquidation_half_shares () {
762+ let (mut scenario, mut clock, admin_cap, maintainer_cap, pool_id) = setup_test ();
763+ scenario.next_tx (test_constants::admin ());
764+ let mut pool = scenario.take_shared_by_id <MarginPool <USDC >>(pool_id);
765+ let registry = scenario.take_shared <MarginRegistry >();
766+
767+ scenario.next_tx (test_constants::user1 ());
768+ let supply_coin = mint_coin <USDC >(10000 * test_constants::usdc_multiplier (), scenario.ctx ());
769+ pool.supply (®istry, supply_coin, option::none (), &clock, scenario.ctx ());
770+
771+ scenario.next_tx (test_constants::user2 ());
772+ let borrow_amount = 1000 * test_constants::usdc_multiplier ();
773+ let (borrowed_coin, _, total_shares) = pool.borrow (borrow_amount, &clock, scenario.ctx ());
774+ destroy (borrowed_coin);
775+
776+ advance_time (&mut clock, margin_constants::year_ms ());
777+
778+ let half_shares = total_shares / 2 ;
779+ let half_amount = pool.borrow_shares_to_amount (half_shares, &clock);
780+ let liquidation_coin = mint_coin <USDC >(half_amount + 10000 , scenario.ctx ());
781+ let (amount, reward, default) = pool.repay_liquidation (half_shares, liquidation_coin, &clock);
782+
783+ assert ! (amount == half_amount);
784+ assert ! (reward == 10000 );
785+ assert ! (default == 0 );
786+
787+ let remaining_shares = total_shares - half_shares;
788+ let remaining_amount = pool.borrow_shares_to_amount (remaining_shares, &clock);
789+ // remaining amount should include accrued interest
790+ assert ! (remaining_amount > borrow_amount / 2 );
791+
792+ test::return_shared (pool);
793+ cleanup_test (registry, admin_cap, maintainer_cap, clock, scenario);
794+ }
795+
796+ #[test]
797+ fun test_partial_liquidation_with_default () {
798+ let (mut scenario, mut clock, admin_cap, maintainer_cap, pool_id) = setup_test ();
799+ scenario.next_tx (test_constants::admin ());
800+ let mut pool = scenario.take_shared_by_id <MarginPool <USDC >>(pool_id);
801+ let registry = scenario.take_shared <MarginRegistry >();
802+
803+ scenario.next_tx (test_constants::user1 ());
804+ let supply_coin = mint_coin <USDC >(10000 * test_constants::usdc_multiplier (), scenario.ctx ());
805+ pool.supply (®istry, supply_coin, option::none (), &clock, scenario.ctx ());
806+
807+ scenario.next_tx (test_constants::user2 ());
808+ let borrow_amount = 2000 * test_constants::usdc_multiplier ();
809+ let (borrowed_coin, _, total_shares) = pool.borrow (borrow_amount, &clock, scenario.ctx ());
810+ destroy (borrowed_coin);
811+
812+ advance_time (&mut clock, margin_constants::year_ms () / 6 );
813+
814+ // Partial liquidation of 30% shares with insufficient payment
815+ let partial_shares = total_shares / 2 ;
816+ let required_amount = pool.borrow_shares_to_amount (partial_shares, &clock);
817+ let insufficient_amount = (required_amount * 90 ) / 100 ;
818+ let liquidation_coin = mint_coin <USDC >(insufficient_amount, scenario.ctx ());
819+ let (amount, reward, default) = pool.repay_liquidation (
820+ partial_shares,
821+ liquidation_coin,
822+ &clock,
823+ );
824+
825+ assert ! (amount == required_amount);
826+ assert ! (reward == 0 );
827+ assert ! (default == required_amount - insufficient_amount);
828+
829+ let remaining_shares = total_shares - partial_shares;
830+ let remaining_amount = pool.borrow_shares_to_amount (remaining_shares, &clock);
831+ assert ! (remaining_amount > 0 );
832+
833+ test::return_shared (pool);
834+ cleanup_test (registry, admin_cap, maintainer_cap, clock, scenario);
835+ }
836+
837+ #[test]
838+ fun test_full_liquidation_with_interest () {
839+ let (mut scenario, mut clock, admin_cap, maintainer_cap, pool_id) = setup_test ();
840+ scenario.next_tx (test_constants::admin ());
841+ let mut pool = scenario.take_shared_by_id <MarginPool <USDC >>(pool_id);
842+ let registry = scenario.take_shared <MarginRegistry >();
843+
844+ scenario.next_tx (test_constants::user1 ());
845+ let supply_amount = 10000 * test_constants::usdc_multiplier ();
846+ let supply_coin = mint_coin <USDC >(supply_amount, scenario.ctx ());
847+ pool.supply (®istry, supply_coin, option::none (), &clock, scenario.ctx ());
848+
849+ scenario.next_tx (test_constants::user2 ());
850+ let borrow_amount = 3000 * test_constants::usdc_multiplier ();
851+ let (borrowed_coin, _, borrow_shares) = pool.borrow (borrow_amount, &clock, scenario.ctx ());
852+ destroy (borrowed_coin);
853+
854+ let initial_debt = pool.borrow_shares_to_amount (borrow_shares, &clock);
855+ assert ! (initial_debt == borrow_amount);
856+
857+ advance_time (&mut clock, margin_constants::year_ms ());
858+
859+ // Check debt has grown substantially due to interest
860+ let debt_after_interest = pool.borrow_shares_to_amount (borrow_shares, &clock);
861+ assert ! (debt_after_interest > initial_debt);
862+
863+ scenario.next_tx (test_constants::liquidator ());
864+ let liquidation_coin = mint_coin <USDC >(debt_after_interest + 1000 , scenario.ctx ());
865+ let (_, reward, default) = pool.repay_liquidation (
866+ borrow_shares,
867+ liquidation_coin,
868+ &clock,
869+ );
870+ assert ! (reward > 0 );
871+ assert ! (default == 0 );
872+
873+ // User should be able to withdraw supply plus interest earned
874+ scenario.next_tx (test_constants::user1 ());
875+ let withdrawn = pool.withdraw (®istry, option::none (), &clock, scenario.ctx ());
876+ let interest_earned = withdrawn.value () - supply_amount;
877+ assert ! (withdrawn.value () > supply_amount);
878+ assert ! (interest_earned > 0 );
879+
880+ destroy (withdrawn);
881+ test::return_shared (pool);
882+ cleanup_test (registry, admin_cap, maintainer_cap, clock, scenario);
883+ }
0 commit comments