Skip to content

Commit d411166

Browse files
author
Sam Barani
authored
(6/n) margin pool liquidation tests (#528)
1 parent 5a1fe55 commit d411166

1 file changed

Lines changed: 236 additions & 1 deletion

File tree

packages/margin_trading/tests/margin_pool_tests.move

Lines changed: 236 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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;
1516
use 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(&registry, 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(&registry, 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(&registry, 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(&registry, 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+
&registry,
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+
&registry,
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(&registry, 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(&registry, 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(&registry, 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(&registry, 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

Comments
 (0)