mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
616 lines
22 KiB
Go
616 lines
22 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package dex
|
|
|
|
import (
|
|
"math/big"
|
|
"testing"
|
|
|
|
"github.com/luxfi/geth/common"
|
|
)
|
|
|
|
// Test addresses for lending
|
|
var (
|
|
testLendingAsset = common.HexToAddress("0x4444444444444444444444444444444444444444")
|
|
testLendingUser1 = common.HexToAddress("0x5555555555555555555555555555555555555555")
|
|
testLendingUser2 = common.HexToAddress("0x6666666666666666666666666666666666666666")
|
|
)
|
|
|
|
// =========================================================================
|
|
// Interest Rate Model Tests
|
|
// =========================================================================
|
|
|
|
func TestInterestRateModel_DefaultModel(t *testing.T) {
|
|
model := DefaultInterestRateModel()
|
|
|
|
if model.BaseRate.Sign() != 0 {
|
|
t.Errorf("expected base rate 0, got %v", model.BaseRate)
|
|
}
|
|
|
|
// Slope1 should be 4% = 0.04 * 1e18
|
|
expectedSlope1 := new(big.Int).Div(new(big.Int).Mul(big.NewInt(4), RAY), big.NewInt(100))
|
|
if model.Slope1.Cmp(expectedSlope1) != 0 {
|
|
t.Errorf("expected slope1 %v, got %v", expectedSlope1, model.Slope1)
|
|
}
|
|
|
|
// OptimalUtilization should be 80%
|
|
expectedOptimal := new(big.Int).Div(new(big.Int).Mul(big.NewInt(80), RAY), big.NewInt(100))
|
|
if model.OptimalUtilization.Cmp(expectedOptimal) != 0 {
|
|
t.Errorf("expected optimal %v, got %v", expectedOptimal, model.OptimalUtilization)
|
|
}
|
|
}
|
|
|
|
func TestInterestRateModel_GetUtilizationRate(t *testing.T) {
|
|
model := DefaultInterestRateModel()
|
|
|
|
// No borrows = 0% utilization
|
|
util := model.GetUtilizationRate(bigInt("1000000000000000000"), big.NewInt(0), big.NewInt(0))
|
|
if util.Sign() != 0 {
|
|
t.Errorf("expected 0 utilization, got %v", util)
|
|
}
|
|
|
|
// 50% utilization: 500 borrowed, 500 cash, 0 reserves
|
|
cash := bigInt("500000000000000000000")
|
|
borrows := bigInt("500000000000000000000")
|
|
util = model.GetUtilizationRate(cash, borrows, big.NewInt(0))
|
|
|
|
// Expected: 500 / 1000 = 0.5 = 50%
|
|
expectedUtil := new(big.Int).Div(RAY, big.NewInt(2))
|
|
if util.Cmp(expectedUtil) != 0 {
|
|
t.Errorf("expected 50%% utilization (%v), got %v", expectedUtil, util)
|
|
}
|
|
}
|
|
|
|
func TestInterestRateModel_GetBorrowRate_BelowKink(t *testing.T) {
|
|
model := DefaultInterestRateModel()
|
|
|
|
// 50% utilization (below 80% kink)
|
|
cash := bigInt("500000000000000000000")
|
|
borrows := bigInt("500000000000000000000")
|
|
|
|
rate := model.GetBorrowRate(cash, borrows, big.NewInt(0))
|
|
|
|
// Rate should be positive
|
|
if rate.Sign() <= 0 {
|
|
t.Errorf("expected positive borrow rate, got %v", rate)
|
|
}
|
|
}
|
|
|
|
func TestInterestRateModel_GetBorrowRate_AboveKink(t *testing.T) {
|
|
model := DefaultInterestRateModel()
|
|
|
|
// 95% utilization (above 80% kink)
|
|
cash := bigInt("50000000000000000000")
|
|
borrows := bigInt("950000000000000000000")
|
|
|
|
rate := model.GetBorrowRate(cash, borrows, big.NewInt(0))
|
|
|
|
// Rate should be higher than at 50% utilization
|
|
rate50 := model.GetBorrowRate(bigInt("500000000000000000000"), bigInt("500000000000000000000"), big.NewInt(0))
|
|
|
|
if rate.Cmp(rate50) <= 0 {
|
|
t.Errorf("expected higher rate above kink, got rate=%v, rate50=%v", rate, rate50)
|
|
}
|
|
}
|
|
|
|
func TestInterestRateModel_GetSupplyRate(t *testing.T) {
|
|
model := DefaultInterestRateModel()
|
|
|
|
cash := bigInt("500000000000000000000")
|
|
borrows := bigInt("500000000000000000000")
|
|
|
|
supplyRate := model.GetSupplyRate(cash, borrows, big.NewInt(0))
|
|
borrowRate := model.GetBorrowRate(cash, borrows, big.NewInt(0))
|
|
|
|
// Supply rate should be less than borrow rate (due to reserve factor)
|
|
if supplyRate.Cmp(borrowRate) >= 0 {
|
|
t.Errorf("expected supply rate < borrow rate")
|
|
}
|
|
}
|
|
|
|
func TestInterestRateModel_AccrueInterest(t *testing.T) {
|
|
model := DefaultInterestRateModel()
|
|
|
|
principal := bigInt("1000000000000000000000") // 1000 tokens
|
|
cash := bigInt("500000000000000000000")
|
|
borrows := bigInt("500000000000000000000")
|
|
blocks := uint64(100)
|
|
|
|
interest := model.AccrueInterest(principal, cash, borrows, big.NewInt(0), blocks)
|
|
|
|
// Interest should be positive
|
|
if interest.Sign() <= 0 {
|
|
t.Errorf("expected positive interest, got %v", interest)
|
|
}
|
|
|
|
// Interest should be less than principal for 100 blocks
|
|
if interest.Cmp(principal) >= 0 {
|
|
t.Errorf("interest too high: %v >= %v", interest, principal)
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
// Lending Pool Tests
|
|
// =========================================================================
|
|
|
|
func TestLendingPool_NewLendingPool(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
lp := NewLendingPool(pm)
|
|
|
|
if lp == nil {
|
|
t.Fatal("NewLendingPool returned nil")
|
|
}
|
|
|
|
if lp.poolManager != pm {
|
|
t.Error("pool manager not set correctly")
|
|
}
|
|
}
|
|
|
|
func TestLendingPool_InitializeReserve(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
lp := NewLendingPool(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// 75% collateral factor
|
|
collateralFactor := new(big.Int).Div(new(big.Int).Mul(big.NewInt(75), RAY), big.NewInt(100))
|
|
// 5% liquidation bonus
|
|
liquidationBonus := new(big.Int).Div(new(big.Int).Mul(big.NewInt(5), RAY), big.NewInt(100))
|
|
|
|
err := lp.InitializeReserve(stateDB, testLendingAsset, collateralFactor, liquidationBonus, DefaultInterestRateModel())
|
|
if err != nil {
|
|
t.Fatalf("InitializeReserve failed: %v", err)
|
|
}
|
|
|
|
// Check reserve was created
|
|
reserve := lp.GetReserve(testLendingAsset)
|
|
if reserve == nil {
|
|
t.Fatal("reserve not found after initialization")
|
|
}
|
|
|
|
if !reserve.IsActive {
|
|
t.Error("reserve should be active")
|
|
}
|
|
|
|
if reserve.CollateralFactor.Cmp(collateralFactor) != 0 {
|
|
t.Errorf("wrong collateral factor: got %v, want %v", reserve.CollateralFactor, collateralFactor)
|
|
}
|
|
}
|
|
|
|
func TestLendingPool_InitializeReserve_AlreadyExists(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
lp := NewLendingPool(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
collateralFactor := new(big.Int).Div(new(big.Int).Mul(big.NewInt(75), RAY), big.NewInt(100))
|
|
liquidationBonus := new(big.Int).Div(new(big.Int).Mul(big.NewInt(5), RAY), big.NewInt(100))
|
|
|
|
lp.InitializeReserve(stateDB, testLendingAsset, collateralFactor, liquidationBonus, DefaultInterestRateModel())
|
|
|
|
// Try to initialize again
|
|
err := lp.InitializeReserve(stateDB, testLendingAsset, collateralFactor, liquidationBonus, DefaultInterestRateModel())
|
|
if err != ErrReserveAlreadyExists {
|
|
t.Errorf("expected ErrReserveAlreadyExists, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLendingPool_Supply(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
lp := NewLendingPool(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Initialize reserve
|
|
collateralFactor := new(big.Int).Div(new(big.Int).Mul(big.NewInt(75), RAY), big.NewInt(100))
|
|
liquidationBonus := new(big.Int).Div(new(big.Int).Mul(big.NewInt(5), RAY), big.NewInt(100))
|
|
lp.InitializeReserve(stateDB, testLendingAsset, collateralFactor, liquidationBonus, DefaultInterestRateModel())
|
|
|
|
// Give user balance
|
|
setBalance(stateDB, testLendingUser1, bigInt("10000000000000000000000")) // 10,000 tokens
|
|
|
|
// Supply 1000 tokens
|
|
supplyAmount := bigInt("1000000000000000000000")
|
|
supplyTokens, err := lp.Supply(stateDB, testLendingUser1, testLendingAsset, supplyAmount)
|
|
if err != nil {
|
|
t.Fatalf("Supply failed: %v", err)
|
|
}
|
|
|
|
// At 1:1 exchange rate, supply tokens should equal supply amount
|
|
if supplyTokens.Cmp(supplyAmount) != 0 {
|
|
t.Errorf("wrong supply tokens: got %v, want %v", supplyTokens, supplyAmount)
|
|
}
|
|
|
|
// Check position
|
|
position := lp.GetPosition(stateDB, testLendingUser1, testLendingAsset)
|
|
if position == nil {
|
|
t.Fatal("position not found")
|
|
}
|
|
|
|
if position.SupplyShares.Cmp(supplyAmount) != 0 {
|
|
t.Errorf("wrong supply shares: got %v, want %v", position.SupplyShares, supplyAmount)
|
|
}
|
|
|
|
// Check reserve total
|
|
reserve := lp.GetReserve(testLendingAsset)
|
|
if reserve.TotalSupply.Cmp(supplyAmount) != 0 {
|
|
t.Errorf("wrong total supply: got %v, want %v", reserve.TotalSupply, supplyAmount)
|
|
}
|
|
}
|
|
|
|
func TestLendingPool_Borrow(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
lp := NewLendingPool(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Initialize reserve with 75% LTV
|
|
collateralFactor := new(big.Int).Div(new(big.Int).Mul(big.NewInt(75), RAY), big.NewInt(100))
|
|
liquidationBonus := new(big.Int).Div(new(big.Int).Mul(big.NewInt(5), RAY), big.NewInt(100))
|
|
lp.InitializeReserve(stateDB, testLendingAsset, collateralFactor, liquidationBonus, DefaultInterestRateModel())
|
|
|
|
// Supply first
|
|
setBalance(stateDB, testLendingUser1, bigInt("10000000000000000000000"))
|
|
supplyAmount := bigInt("1000000000000000000000") // 1000 tokens
|
|
lp.Supply(stateDB, testLendingUser1, testLendingAsset, supplyAmount)
|
|
|
|
// Borrow 500 tokens (50% of collateral, well below 75% limit)
|
|
borrowAmount := bigInt("500000000000000000000")
|
|
err := lp.Borrow(stateDB, testLendingUser1, testLendingAsset, borrowAmount)
|
|
if err != nil {
|
|
t.Fatalf("Borrow failed: %v", err)
|
|
}
|
|
|
|
// Check position
|
|
position := lp.GetPosition(stateDB, testLendingUser1, testLendingAsset)
|
|
if position.BorrowAmount.Cmp(borrowAmount) != 0 {
|
|
t.Errorf("wrong borrow amount: got %v, want %v", position.BorrowAmount, borrowAmount)
|
|
}
|
|
|
|
// Check reserve
|
|
reserve := lp.GetReserve(testLendingAsset)
|
|
if reserve.TotalBorrows.Cmp(borrowAmount) != 0 {
|
|
t.Errorf("wrong total borrows: got %v, want %v", reserve.TotalBorrows, borrowAmount)
|
|
}
|
|
}
|
|
|
|
func TestLendingPool_Borrow_ExceedsLTV(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
lp := NewLendingPool(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Initialize reserve with 75% LTV
|
|
collateralFactor := new(big.Int).Div(new(big.Int).Mul(big.NewInt(75), RAY), big.NewInt(100))
|
|
liquidationBonus := new(big.Int).Div(new(big.Int).Mul(big.NewInt(5), RAY), big.NewInt(100))
|
|
lp.InitializeReserve(stateDB, testLendingAsset, collateralFactor, liquidationBonus, DefaultInterestRateModel())
|
|
|
|
// Supply 1000 tokens
|
|
setBalance(stateDB, testLendingUser1, bigInt("10000000000000000000000"))
|
|
supplyAmount := bigInt("1000000000000000000000")
|
|
lp.Supply(stateDB, testLendingUser1, testLendingAsset, supplyAmount)
|
|
|
|
// Try to borrow 800 tokens (80% > 75% limit)
|
|
borrowAmount := bigInt("800000000000000000000")
|
|
err := lp.Borrow(stateDB, testLendingUser1, testLendingAsset, borrowAmount)
|
|
if err != ErrMaxLTVExceeded {
|
|
t.Errorf("expected ErrMaxLTVExceeded, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLendingPool_Repay(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
lp := NewLendingPool(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Initialize reserve
|
|
collateralFactor := new(big.Int).Div(new(big.Int).Mul(big.NewInt(75), RAY), big.NewInt(100))
|
|
liquidationBonus := new(big.Int).Div(new(big.Int).Mul(big.NewInt(5), RAY), big.NewInt(100))
|
|
lp.InitializeReserve(stateDB, testLendingAsset, collateralFactor, liquidationBonus, DefaultInterestRateModel())
|
|
|
|
// Supply and borrow
|
|
setBalance(stateDB, testLendingUser1, bigInt("10000000000000000000000"))
|
|
supplyAmount := bigInt("1000000000000000000000")
|
|
lp.Supply(stateDB, testLendingUser1, testLendingAsset, supplyAmount)
|
|
|
|
borrowAmount := bigInt("500000000000000000000")
|
|
lp.Borrow(stateDB, testLendingUser1, testLendingAsset, borrowAmount)
|
|
|
|
// Repay half
|
|
repayAmount := bigInt("250000000000000000000")
|
|
actualRepaid, err := lp.Repay(stateDB, testLendingUser1, testLendingAsset, repayAmount)
|
|
if err != nil {
|
|
t.Fatalf("Repay failed: %v", err)
|
|
}
|
|
|
|
if actualRepaid.Cmp(repayAmount) != 0 {
|
|
t.Errorf("wrong repaid amount: got %v, want %v", actualRepaid, repayAmount)
|
|
}
|
|
|
|
// Check position
|
|
position := lp.GetPosition(stateDB, testLendingUser1, testLendingAsset)
|
|
expectedDebt := new(big.Int).Sub(borrowAmount, repayAmount)
|
|
if position.BorrowAmount.Cmp(expectedDebt) != 0 {
|
|
t.Errorf("wrong debt after repay: got %v, want %v", position.BorrowAmount, expectedDebt)
|
|
}
|
|
}
|
|
|
|
func TestLendingPool_Withdraw(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
lp := NewLendingPool(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Initialize reserve
|
|
collateralFactor := new(big.Int).Div(new(big.Int).Mul(big.NewInt(75), RAY), big.NewInt(100))
|
|
liquidationBonus := new(big.Int).Div(new(big.Int).Mul(big.NewInt(5), RAY), big.NewInt(100))
|
|
lp.InitializeReserve(stateDB, testLendingAsset, collateralFactor, liquidationBonus, DefaultInterestRateModel())
|
|
|
|
// Supply
|
|
setBalance(stateDB, testLendingUser1, bigInt("10000000000000000000000"))
|
|
supplyAmount := bigInt("1000000000000000000000")
|
|
lp.Supply(stateDB, testLendingUser1, testLendingAsset, supplyAmount)
|
|
|
|
// Withdraw half
|
|
withdrawShares := bigInt("500000000000000000000")
|
|
withdrawn, err := lp.Withdraw(stateDB, testLendingUser1, testLendingAsset, withdrawShares)
|
|
if err != nil {
|
|
t.Fatalf("Withdraw failed: %v", err)
|
|
}
|
|
|
|
// At 1:1 exchange rate, withdrawn should equal shares
|
|
if withdrawn.Cmp(withdrawShares) != 0 {
|
|
t.Errorf("wrong withdrawn amount: got %v, want %v", withdrawn, withdrawShares)
|
|
}
|
|
|
|
// Check position
|
|
position := lp.GetPosition(stateDB, testLendingUser1, testLendingAsset)
|
|
expectedShares := new(big.Int).Sub(supplyAmount, withdrawShares)
|
|
if position.SupplyShares.Cmp(expectedShares) != 0 {
|
|
t.Errorf("wrong shares after withdraw: got %v, want %v", position.SupplyShares, expectedShares)
|
|
}
|
|
}
|
|
|
|
func TestLendingPool_Withdraw_WithDebt(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
lp := NewLendingPool(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Initialize reserve with 75% LTV
|
|
collateralFactor := new(big.Int).Div(new(big.Int).Mul(big.NewInt(75), RAY), big.NewInt(100))
|
|
liquidationBonus := new(big.Int).Div(new(big.Int).Mul(big.NewInt(5), RAY), big.NewInt(100))
|
|
lp.InitializeReserve(stateDB, testLendingAsset, collateralFactor, liquidationBonus, DefaultInterestRateModel())
|
|
|
|
// Supply 1000 tokens
|
|
setBalance(stateDB, testLendingUser1, bigInt("10000000000000000000000"))
|
|
supplyAmount := bigInt("1000000000000000000000")
|
|
lp.Supply(stateDB, testLendingUser1, testLendingAsset, supplyAmount)
|
|
|
|
// Borrow 500 tokens (50% LTV)
|
|
borrowAmount := bigInt("500000000000000000000")
|
|
lp.Borrow(stateDB, testLendingUser1, testLendingAsset, borrowAmount)
|
|
|
|
// Try to withdraw too much (would break health factor)
|
|
// With 500 debt and 75% LTV, need at least 667 collateral
|
|
// Withdrawing 400 would leave 600, which is below minimum
|
|
withdrawShares := bigInt("400000000000000000000")
|
|
_, err := lp.Withdraw(stateDB, testLendingUser1, testLendingAsset, withdrawShares)
|
|
if err != ErrHealthFactorTooLow {
|
|
t.Errorf("expected ErrHealthFactorTooLow, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLendingPool_GetHealthFactor(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
lp := NewLendingPool(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Initialize reserve with 75% LTV
|
|
collateralFactor := new(big.Int).Div(new(big.Int).Mul(big.NewInt(75), RAY), big.NewInt(100))
|
|
liquidationBonus := new(big.Int).Div(new(big.Int).Mul(big.NewInt(5), RAY), big.NewInt(100))
|
|
lp.InitializeReserve(stateDB, testLendingAsset, collateralFactor, liquidationBonus, DefaultInterestRateModel())
|
|
|
|
// Supply 1000 tokens
|
|
setBalance(stateDB, testLendingUser1, bigInt("10000000000000000000000"))
|
|
supplyAmount := bigInt("1000000000000000000000")
|
|
lp.Supply(stateDB, testLendingUser1, testLendingAsset, supplyAmount)
|
|
|
|
// Borrow 500 tokens
|
|
borrowAmount := bigInt("500000000000000000000")
|
|
lp.Borrow(stateDB, testLendingUser1, testLendingAsset, borrowAmount)
|
|
|
|
// Health factor = (1000 * 0.75) / 500 = 1.5
|
|
healthFactor := lp.GetHealthFactor(stateDB, testLendingUser1, testLendingAsset)
|
|
|
|
// Expected: 1.5 * RAY
|
|
expectedHF := new(big.Int).Mul(RAY, big.NewInt(3))
|
|
expectedHF.Div(expectedHF, big.NewInt(2))
|
|
|
|
if healthFactor.Cmp(expectedHF) != 0 {
|
|
t.Errorf("wrong health factor: got %v, want %v", healthFactor, expectedHF)
|
|
}
|
|
}
|
|
|
|
func TestLendingPool_GetUserAccountData(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
lp := NewLendingPool(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Initialize reserve with 75% LTV
|
|
collateralFactor := new(big.Int).Div(new(big.Int).Mul(big.NewInt(75), RAY), big.NewInt(100))
|
|
liquidationBonus := new(big.Int).Div(new(big.Int).Mul(big.NewInt(5), RAY), big.NewInt(100))
|
|
lp.InitializeReserve(stateDB, testLendingAsset, collateralFactor, liquidationBonus, DefaultInterestRateModel())
|
|
|
|
// Supply 1000 tokens
|
|
setBalance(stateDB, testLendingUser1, bigInt("10000000000000000000000"))
|
|
supplyAmount := bigInt("1000000000000000000000")
|
|
lp.Supply(stateDB, testLendingUser1, testLendingAsset, supplyAmount)
|
|
|
|
// Borrow 500 tokens
|
|
borrowAmount := bigInt("500000000000000000000")
|
|
lp.Borrow(stateDB, testLendingUser1, testLendingAsset, borrowAmount)
|
|
|
|
supplyValue, borrowValue, availableToBorrow, healthFactor := lp.GetUserAccountData(stateDB, testLendingUser1, testLendingAsset)
|
|
|
|
// Supply value should be 1000
|
|
if supplyValue.Cmp(supplyAmount) != 0 {
|
|
t.Errorf("wrong supply value: got %v, want %v", supplyValue, supplyAmount)
|
|
}
|
|
|
|
// Borrow value should be 500
|
|
if borrowValue.Cmp(borrowAmount) != 0 {
|
|
t.Errorf("wrong borrow value: got %v, want %v", borrowValue, borrowAmount)
|
|
}
|
|
|
|
// Available to borrow = (1000 * 0.75) - 500 = 250
|
|
expectedAvailable := bigInt("250000000000000000000")
|
|
if availableToBorrow.Cmp(expectedAvailable) != 0 {
|
|
t.Errorf("wrong available to borrow: got %v, want %v", availableToBorrow, expectedAvailable)
|
|
}
|
|
|
|
// Health factor should be > 1 (position is healthy)
|
|
if healthFactor.Cmp(RAY) <= 0 {
|
|
t.Errorf("health factor should be > 1, got %v", healthFactor)
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
// Liquidator Tests
|
|
// =========================================================================
|
|
|
|
func TestLiquidator_NewLiquidator(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
lp := NewLendingPool(pm)
|
|
liquidator := NewLiquidator(lp)
|
|
|
|
if liquidator == nil {
|
|
t.Fatal("NewLiquidator returned nil")
|
|
}
|
|
|
|
if liquidator.lendingPool != lp {
|
|
t.Error("lending pool not set correctly")
|
|
}
|
|
}
|
|
|
|
func TestLiquidator_DefaultConfig(t *testing.T) {
|
|
config := DefaultLiquidatorConfig()
|
|
|
|
// Close factor should be 50%
|
|
expectedCloseFactor := new(big.Int).Div(new(big.Int).Mul(big.NewInt(50), RAY), big.NewInt(100))
|
|
if config.CloseFactor.Cmp(expectedCloseFactor) != 0 {
|
|
t.Errorf("wrong close factor: got %v, want %v", config.CloseFactor, expectedCloseFactor)
|
|
}
|
|
|
|
// Threshold should be 1.0
|
|
if config.LiquidationThreshold.Cmp(RAY) != 0 {
|
|
t.Errorf("wrong threshold: got %v, want %v", config.LiquidationThreshold, RAY)
|
|
}
|
|
}
|
|
|
|
func TestLiquidator_IsLiquidatable(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
lp := NewLendingPool(pm)
|
|
liquidator := NewLiquidator(lp)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Initialize reserve with 75% LTV
|
|
collateralFactor := new(big.Int).Div(new(big.Int).Mul(big.NewInt(75), RAY), big.NewInt(100))
|
|
liquidationBonus := new(big.Int).Div(new(big.Int).Mul(big.NewInt(5), RAY), big.NewInt(100))
|
|
lp.InitializeReserve(stateDB, testLendingAsset, collateralFactor, liquidationBonus, DefaultInterestRateModel())
|
|
|
|
// Supply 1000 tokens
|
|
setBalance(stateDB, testLendingUser1, bigInt("10000000000000000000000"))
|
|
supplyAmount := bigInt("1000000000000000000000")
|
|
lp.Supply(stateDB, testLendingUser1, testLendingAsset, supplyAmount)
|
|
|
|
// Borrow 500 tokens (healthy position)
|
|
borrowAmount := bigInt("500000000000000000000")
|
|
lp.Borrow(stateDB, testLendingUser1, testLendingAsset, borrowAmount)
|
|
|
|
// Should not be liquidatable
|
|
if liquidator.IsLiquidatable(stateDB, testLendingUser1, testLendingAsset) {
|
|
t.Error("position should not be liquidatable")
|
|
}
|
|
}
|
|
|
|
func TestLiquidator_GetLiquidatableAmount(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
lp := NewLendingPool(pm)
|
|
liquidator := NewLiquidator(lp)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Initialize reserve
|
|
collateralFactor := new(big.Int).Div(new(big.Int).Mul(big.NewInt(75), RAY), big.NewInt(100))
|
|
liquidationBonus := new(big.Int).Div(new(big.Int).Mul(big.NewInt(5), RAY), big.NewInt(100))
|
|
lp.InitializeReserve(stateDB, testLendingAsset, collateralFactor, liquidationBonus, DefaultInterestRateModel())
|
|
|
|
// Supply and borrow (healthy position)
|
|
setBalance(stateDB, testLendingUser1, bigInt("10000000000000000000000"))
|
|
supplyAmount := bigInt("1000000000000000000000")
|
|
lp.Supply(stateDB, testLendingUser1, testLendingAsset, supplyAmount)
|
|
|
|
borrowAmount := bigInt("500000000000000000000")
|
|
lp.Borrow(stateDB, testLendingUser1, testLendingAsset, borrowAmount)
|
|
|
|
// Liquidatable amount should be 0 (position is healthy)
|
|
liquidatableAmount := liquidator.GetLiquidatableAmount(stateDB, testLendingUser1, testLendingAsset)
|
|
if liquidatableAmount.Sign() != 0 {
|
|
t.Errorf("expected 0 liquidatable amount, got %v", liquidatableAmount)
|
|
}
|
|
}
|
|
|
|
func TestLendingPool_FullFlow(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
lp := NewLendingPool(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Initialize reserve
|
|
collateralFactor := new(big.Int).Div(new(big.Int).Mul(big.NewInt(75), RAY), big.NewInt(100))
|
|
liquidationBonus := new(big.Int).Div(new(big.Int).Mul(big.NewInt(5), RAY), big.NewInt(100))
|
|
lp.InitializeReserve(stateDB, testLendingAsset, collateralFactor, liquidationBonus, DefaultInterestRateModel())
|
|
|
|
// User 1 supplies 1000 tokens
|
|
setBalance(stateDB, testLendingUser1, bigInt("10000000000000000000000"))
|
|
supply1 := bigInt("1000000000000000000000")
|
|
lp.Supply(stateDB, testLendingUser1, testLendingAsset, supply1)
|
|
|
|
// User 2 supplies 500 tokens
|
|
setBalance(stateDB, testLendingUser2, bigInt("10000000000000000000000"))
|
|
supply2 := bigInt("500000000000000000000")
|
|
lp.Supply(stateDB, testLendingUser2, testLendingAsset, supply2)
|
|
|
|
// User 1 borrows 400 tokens
|
|
borrow1 := bigInt("400000000000000000000")
|
|
err := lp.Borrow(stateDB, testLendingUser1, testLendingAsset, borrow1)
|
|
if err != nil {
|
|
t.Fatalf("User 1 borrow failed: %v", err)
|
|
}
|
|
|
|
// Check total supply
|
|
reserve := lp.GetReserve(testLendingAsset)
|
|
expectedTotalSupply := new(big.Int).Add(supply1, supply2)
|
|
if reserve.TotalSupply.Cmp(expectedTotalSupply) != 0 {
|
|
t.Errorf("wrong total supply: got %v, want %v", reserve.TotalSupply, expectedTotalSupply)
|
|
}
|
|
|
|
// User 1 repays full debt
|
|
lp.Repay(stateDB, testLendingUser1, testLendingAsset, borrow1)
|
|
|
|
// Check debt is 0
|
|
position := lp.GetPosition(stateDB, testLendingUser1, testLendingAsset)
|
|
if position.BorrowAmount.Sign() != 0 {
|
|
t.Errorf("debt should be 0 after full repay, got %v", position.BorrowAmount)
|
|
}
|
|
|
|
// User 1 withdraws all
|
|
lp.Withdraw(stateDB, testLendingUser1, testLendingAsset, supply1)
|
|
|
|
// Check user 1 has no shares
|
|
position = lp.GetPosition(stateDB, testLendingUser1, testLendingAsset)
|
|
if position.SupplyShares.Sign() != 0 {
|
|
t.Errorf("shares should be 0 after full withdraw, got %v", position.SupplyShares)
|
|
}
|
|
|
|
t.Logf("Full lending flow completed:")
|
|
t.Logf(" User 1 supplied: %v", supply1)
|
|
t.Logf(" User 2 supplied: %v", supply2)
|
|
t.Logf(" User 1 borrowed: %v", borrow1)
|
|
t.Logf(" User 1 repaid: %v", borrow1)
|
|
t.Logf(" Total supply remaining: %v", reserve.TotalSupply)
|
|
}
|