From c08d07276655c58932aae2ea9a33a7c8925444c8 Mon Sep 17 00:00:00 2001
From: toteki <63419657+toteki@users.noreply.github.com>
Date: Wed, 8 Nov 2023 07:43:01 -0700
Subject: [PATCH] going through tests

---
 x/leverage/types/position.go      | 44 ++++++++++++++++++++++++-
 x/leverage/types/position_test.go | 54 ++++++++++++++++++++++++-------
 2 files changed, 85 insertions(+), 13 deletions(-)

diff --git a/x/leverage/types/position.go b/x/leverage/types/position.go
index aec2902ed7..a5a8f08848 100644
--- a/x/leverage/types/position.go
+++ b/x/leverage/types/position.go
@@ -256,12 +256,40 @@ func (ap *AccountPosition) Limit() sdk.Dec {
 	// compute limit due to collateral weights
 	limit := ap.totalBorrowLimit()
 
+	// if no borrows, borrow factor limits will not apply
+	borrowedValue := ap.BorrowedValue()
+	if borrowedValue.IsZero() {
+		return limit
+	}
+
 	// compute limit due to borrow factors
 	usage := ap.totalCollateralUsage()
-	avgWeight := ap.normalBorrowLimit().Quo(collateralValue)
 	unusedCollateralValue := ap.CollateralValue().Sub(usage) // can be negative
+
+	var avgWeight sdk.Dec
+	if unusedCollateralValue.IsPositive() {
+		// if user is below limit, unused collateral can be borrowed against at its average collateral weight at most
+		avgWeight = ap.averageWeight(ap.collateralValue)
+	} else {
+		// if user if above limit, overused collateral is being borrowed against at borrow factor
+		avgWeight = borrowedValue.Quo(usage)
+	}
 	borrowFactorLimit := ap.BorrowedValue().Add(unusedCollateralValue.Mul(avgWeight))
 
+	if len(ap.collateralValue) == 1 && ap.collateralValue[0].Denom == "FFFF" {
+		if len(ap.borrowedValue) == 1 && ap.borrowedValue[0].Denom == "HHHH" {
+			fmt.Printf("%s -> %s (%t)\n  %s, %s\n    >>> %s\n",
+				ap.collateralValue,
+				ap.borrowedValue,
+				ap.isForLiquidation,
+				limit,
+				borrowFactorLimit,
+				usage,
+			)
+		}
+	}
+
+	// return the minimum of the two limits
 	return sdk.MinDec(limit, borrowFactorLimit)
 }
 
@@ -300,6 +328,20 @@ func (ap *AccountPosition) tokenWeight(denom string) sdk.Dec {
 	return sdk.ZeroDec()
 }
 
+// averageWeight gets the weighted average collateral weight (or liquidation threshold) of a set of tokens
+func (ap *AccountPosition) averageWeight(coins sdk.DecCoins) sdk.Dec {
+	if coins.IsZero() {
+		return sdk.OneDec()
+	}
+	valueSum := sdk.ZeroDec()
+	weightSum := sdk.ZeroDec()
+	for _, c := range coins {
+		weightSum = weightSum.Add(c.Amount.Mul(ap.tokenWeight(c.Denom)))
+		valueSum = valueSum.Add(c.Amount)
+	}
+	return weightSum.Quo(valueSum)
+}
+
 // borrowFactor gets a token's collateral weight or liquidation threshold (or minimumBorrowFactor if greater)
 // if the token is registered, else zero.
 func (ap *AccountPosition) borrowFactor(denom string) sdk.Dec {
diff --git a/x/leverage/types/position_test.go b/x/leverage/types/position_test.go
index fa3dc954da..6125aa82f2 100644
--- a/x/leverage/types/position_test.go
+++ b/x/leverage/types/position_test.go
@@ -164,17 +164,17 @@ func TestBorrowLimit(t *testing.T) {
 		{
 			// multiple assets, one with zero weight, at borrow limit
 			sdk.NewDecCoins(
-				coin.Dec("AAAA", "100"),
-				coin.Dec("GGGG", "100"),
-				coin.Dec("IIII", "100"),
+				coin.Dec("AAAA", "100"), // $10, $15
+				coin.Dec("GGGG", "100"), // $70, $75
+				coin.Dec("IIII", "100"), // $0, $95
 			),
 			sdk.NewDecCoins(
-				coin.Dec("GGGG", "80"),
+				coin.Dec("GGGG", "80"), // uses $114.2 or $106.6 of collateral
 			),
-			// effectiveness of I collateral is reduced to due to G liquidation threshold, thus leading
-			// to a lower liquidation threshold than "simple AGI" test case above
+			// effectiveness of I collateral would be reduced to due to G liquidation threshold,
+			// but ordinary liquidation threshold is already more restrictive than borrow factor here
 			"80.00",
-			"165.00",
+			"185.00",
 			"AGI -> G at borrow limit",
 		},
 		{
@@ -185,12 +185,12 @@ func TestBorrowLimit(t *testing.T) {
 				coin.Dec("IIII", "100"),
 			),
 			sdk.NewDecCoins(
-				coin.Dec("GGGG", "165"),
+				coin.Dec("GGGG", "185"),
 			),
 			// significantly over borrow limit, so calculation subtracts value of unpaired borrows
-			// from total borrowed value to determine borrow limit
+			// from total borrowed value to determine borrow limit to arrive at the same result
 			"80.00",
-			"165.00",
+			"185.00",
 			"AGI -> G at liquidation threshold",
 		},
 		{
@@ -206,7 +206,7 @@ func TestBorrowLimit(t *testing.T) {
 			// significantly over borrow limit and liquidation threshold, but calculation still reaches
 			// the same values for them
 			"80.00",
-			"165.00",
+			"185.00",
 			"AGI -> G above liquidation threshold",
 		},
 		{
@@ -250,6 +250,36 @@ func TestBorrowLimit(t *testing.T) {
 			"53.00",
 			"F -> A",
 		},
+		{
+			// single asset with unused special pair (borrowFactor reducing weight, minimumBorrowFactor, at limit)
+			sdk.NewDecCoins(
+				coin.Dec("FFFF", "100"),
+			),
+			sdk.NewDecCoins(
+				coin.Dec("AAAA", "50"),
+			),
+			// 50 A consumes 100 F collateral (weight 0.5 due to MinimumBorrowFactor)
+			// the F <-> H special pair has no effect
+			"50.00",
+			"50.00",
+			"F -> A",
+		},
+		{
+			// single asset with unused special pair (borrowFactor, minimumBorrowFactor, over limits)
+			sdk.NewDecCoins(
+				coin.Dec("FFFF", "100"),
+			),
+			sdk.NewDecCoins(
+				coin.Dec("AAAA", "80"),
+			),
+			// 80 A would consume 160 F collateral (weight 0.5 due to MinimumBorrowFactor),
+			// meanwhile 100F on its own would have 60, 65 borrow limit and liquidation threshold.
+			// The calculation works backwards from the 160/80 collateral usage to find the limit at 100
+			// the F <-> H special pair has no effect
+			"50.00",
+			"50.00",
+			"F -> A over limits",
+		},
 		{
 			// single asset with special pair in effect
 			sdk.NewDecCoins(
@@ -291,7 +321,7 @@ func TestBorrowLimit(t *testing.T) {
 			),
 			// 60 H consumes all 100 F collateral (weight 0.6 due to Special Pair).
 			// A remaining 20H is unpaired borrowed value. Borrow limit equals value minus unpaired.
-			// Meanwhile, 80A consumes 100 F collateral (liquidation threshold 0.8 due to special pair).
+			// Meanwhile, 80 H consumes 100 F collateral (liquidation threshold 0.8 due to special pair).
 			// Liquidation threshold is exactly borrowed value.
 			"60.00",
 			"80.00",