Hard: LTV index refactor (#758)

* add set/delete/update ltv methods

* refactor borrow logic

* basic updates to keeper logic for compile

* Add deposit index set/delete/update keeper methods

* refactor deposit logic

* refactor repay logic

* update withdraw logic

* introduce DeleteDepositBorrowAndLtvIndex

* remove unused bool from AttemptKeeperLiquidation

* remove comments (transitioned to asana cards)

* catch multiple error types in liquidation loop
This commit is contained in:
Denali Marsh 2021-01-07 22:40:25 +01:00 committed by GitHub
parent 38306e5465
commit bc110ce609
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 80 additions and 65 deletions

View File

@ -124,7 +124,7 @@ func handleMsgRepay(ctx sdk.Context, k keeper.Keeper, msg types.MsgRepay) (*sdk.
} }
func handleMsgLiquidate(ctx sdk.Context, k keeper.Keeper, msg types.MsgLiquidate) (*sdk.Result, error) { func handleMsgLiquidate(ctx sdk.Context, k keeper.Keeper, msg types.MsgLiquidate) (*sdk.Result, error) {
_, err := k.AttemptKeeperLiquidation(ctx, msg.Keeper, msg.Borrower) err := k.AttemptKeeperLiquidation(ctx, msg.Keeper, msg.Borrower)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -23,7 +23,7 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins
} }
// Get current stored LTV based on stored borrows/deposits // Get current stored LTV based on stored borrows/deposits
prevLtv, shouldRemoveIndex, err := k.GetStoreLTV(ctx, borrower) prevLtv, err := k.GetStoreLTV(ctx, borrower)
if err != nil { if err != nil {
return err return err
} }
@ -85,11 +85,20 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins
amount = coins amount = coins
} }
// Update the borrower's amount and borrow interest factors in the store // Construct the user's new/updated borrow with amount and interest factors
borrow := types.NewBorrow(borrower, amount, borrowInterestFactors) borrow := types.NewBorrow(borrower, amount, borrowInterestFactors)
k.SetBorrow(ctx, borrow)
k.UpdateItemInLtvIndex(ctx, prevLtv, shouldRemoveIndex, borrower) // Calculate the new Loan-to-Value ratio of Deposit-to-Borrow
deposit, foundDeposit := k.GetDeposit(ctx, borrower)
if !foundDeposit {
return types.ErrDepositNotFound
}
newLtv, err := k.CalculateLtv(ctx, deposit, borrow)
if err != nil {
return err
}
k.UpdateBorrowAndLtvIndex(ctx, borrow, newLtv, prevLtv)
// Update total borrowed amount by newly borrowed coins. Don't add user's pending interest as // Update total borrowed amount by newly borrowed coins. Don't add user's pending interest as
// it has already been included in the total borrowed coins by the BeginBlocker. // it has already been included in the total borrowed coins by the BeginBlocker.

View File

@ -24,7 +24,7 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coi
} }
// Get current stored LTV based on stored borrows/deposits // Get current stored LTV based on stored borrows/deposits
prevLtv, shouldRemoveIndex, err := k.GetStoreLTV(ctx, depositor) prevLtv, err := k.GetStoreLTV(ctx, depositor)
if err != nil { if err != nil {
return err return err
} }
@ -87,12 +87,17 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coi
} else { } else {
amount = coins amount = coins
} }
// Update the depositer's amount and supply interest factors in the store // Update the depositer's amount and supply interest factors in the store
deposit := types.NewDeposit(depositor, amount, supplyInterestFactors) deposit := types.NewDeposit(depositor, amount, supplyInterestFactors)
k.SetDeposit(ctx, deposit)
k.UpdateItemInLtvIndex(ctx, prevLtv, shouldRemoveIndex, depositor) // Calculate the new Loan-to-Value ratio of Deposit-to-Borrow
borrow, _ := k.GetBorrow(ctx, depositor)
newLtv, err := k.CalculateLtv(ctx, deposit, borrow)
if err != nil {
return err
}
k.UpdateDepositAndLtvIndex(ctx, deposit, newLtv, prevLtv)
// Update total supplied amount by newly supplied coins. Don't add user's pending interest as // Update total supplied amount by newly supplied coins. Don't add user's pending interest as
// it has already been included in the total supplied coins by the BeginBlocker. // it has already been included in the total supplied coins by the BeginBlocker.

View File

@ -22,9 +22,9 @@ func (k Keeper) AttemptIndexLiquidations(ctx sdk.Context) error {
borrowers := k.GetLtvIndexSlice(ctx, params.CheckLtvIndexCount) borrowers := k.GetLtvIndexSlice(ctx, params.CheckLtvIndexCount)
for _, borrower := range borrowers { for _, borrower := range borrowers {
_, err := k.AttemptKeeperLiquidation(ctx, sdk.AccAddress(types.LiquidatorAccount), borrower) err := k.AttemptKeeperLiquidation(ctx, sdk.AccAddress(types.LiquidatorAccount), borrower)
if err != nil { if err != nil {
if !errors.Is(err, types.ErrBorrowNotLiquidatable) { if !errors.Is(err, types.ErrBorrowNotLiquidatable) && !errors.Is(err, types.ErrBorrowNotFound) {
panic(err) panic(err)
} }
} }
@ -33,34 +33,30 @@ func (k Keeper) AttemptIndexLiquidations(ctx sdk.Context) error {
} }
// AttemptKeeperLiquidation enables a keeper to liquidate an individual borrower's position // AttemptKeeperLiquidation enables a keeper to liquidate an individual borrower's position
func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress, borrower sdk.AccAddress) (bool, error) { func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress, borrower sdk.AccAddress) error {
prevLtv, shouldInsertIndex, err := k.GetStoreLTV(ctx, borrower) prevLtv, err := k.GetStoreLTV(ctx, borrower)
if err != nil { if err != nil {
return false, err return err
} }
k.SyncBorrowInterest(ctx, borrower) k.SyncBorrowInterest(ctx, borrower)
k.UpdateItemInLtvIndex(ctx, prevLtv, shouldInsertIndex, borrower)
// Fetch deposits and parse coin denoms
deposit, found := k.GetDeposit(ctx, borrower) deposit, found := k.GetDeposit(ctx, borrower)
if !found { if !found {
return false, sdkerrors.Wrapf(types.ErrDepositsNotFound, "no deposits found for %s", borrower) return types.ErrDepositNotFound
} }
// Fetch borrow balances and parse coin denoms
borrow, found := k.GetBorrow(ctx, borrower) borrow, found := k.GetBorrow(ctx, borrower)
if !found { if !found {
return false, types.ErrBorrowNotFound return types.ErrBorrowNotFound
} }
isWithinRange, err := k.IsWithinValidLtvRange(ctx, deposit, borrow) isWithinRange, err := k.IsWithinValidLtvRange(ctx, deposit, borrow)
if err != nil { if err != nil {
return false, err return err
} }
if isWithinRange { if isWithinRange {
return false, sdkerrors.Wrapf(types.ErrBorrowNotLiquidatable, "position is within valid LTV range") return sdkerrors.Wrapf(types.ErrBorrowNotLiquidatable, "position is within valid LTV range")
} }
// Sending coins to auction module with keeper address getting % of the profits // Sending coins to auction module with keeper address getting % of the profits
@ -68,19 +64,11 @@ func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress,
depositDenoms := getDenoms(deposit.Amount) depositDenoms := getDenoms(deposit.Amount)
err = k.SeizeDeposits(ctx, keeper, deposit, borrow, depositDenoms, borrowDenoms) err = k.SeizeDeposits(ctx, keeper, deposit, borrow, depositDenoms, borrowDenoms)
if err != nil { if err != nil {
return false, err return err
} }
currLtv, _, err := k.GetStoreLTV(ctx, borrower) k.DeleteDepositBorrowAndLtvIndex(ctx, deposit, borrow, prevLtv)
if err != nil { return nil
return false, err
}
k.RemoveFromLtvIndex(ctx, currLtv, borrower)
k.DeleteBorrow(ctx, borrow)
k.DeleteDeposit(ctx, deposit)
return true, err
} }
// SeizeDeposits seizes a list of deposits and sends them to auction // SeizeDeposits seizes a list of deposits and sends them to auction
@ -276,36 +264,40 @@ func (k Keeper) IsWithinValidLtvRange(ctx sdk.Context, deposit types.Deposit, bo
return true, nil return true, nil
} }
// UpdateItemInLtvIndex updates the key a borrower's address is stored under in the LTV index // UpdateBorrowAndLtvIndex updates a borrow and its LTV index value in the store
func (k Keeper) UpdateItemInLtvIndex(ctx sdk.Context, prevLtv sdk.Dec, func (k Keeper) UpdateBorrowAndLtvIndex(ctx sdk.Context, borrow types.Borrow, newLtv, oldLtv sdk.Dec) {
shouldRemoveIndex bool, borrower sdk.AccAddress) error { k.RemoveFromLtvIndex(ctx, oldLtv, borrow.Borrower)
currLtv, shouldInsertIndex, err := k.GetStoreLTV(ctx, borrower) k.SetBorrow(ctx, borrow)
if err != nil { k.InsertIntoLtvIndex(ctx, newLtv, borrow.Borrower)
return err }
}
if shouldRemoveIndex { // UpdateDepositAndLtvIndex updates a deposit and its LTV index value in the store
k.RemoveFromLtvIndex(ctx, prevLtv, borrower) func (k Keeper) UpdateDepositAndLtvIndex(ctx sdk.Context, deposit types.Deposit, newLtv, oldLtv sdk.Dec) {
} k.RemoveFromLtvIndex(ctx, oldLtv, deposit.Depositor)
if shouldInsertIndex { k.SetDeposit(ctx, deposit)
k.InsertIntoLtvIndex(ctx, currLtv, borrower) k.InsertIntoLtvIndex(ctx, newLtv, deposit.Depositor)
} }
return nil
// DeleteDepositBorrowAndLtvIndex deletes deposit, borrow, and ltv index
func (k Keeper) DeleteDepositBorrowAndLtvIndex(ctx sdk.Context, deposit types.Deposit, borrow types.Borrow, oldLtv sdk.Dec) {
k.RemoveFromLtvIndex(ctx, oldLtv, deposit.Depositor)
k.DeleteDeposit(ctx, deposit)
k.DeleteBorrow(ctx, borrow)
} }
// GetStoreLTV calculates the user's current LTV based on their deposits/borrows in the store // GetStoreLTV calculates the user's current LTV based on their deposits/borrows in the store
// and does not include any outsanding interest. // and does not include any outsanding interest.
func (k Keeper) GetStoreLTV(ctx sdk.Context, addr sdk.AccAddress) (sdk.Dec, bool, error) { func (k Keeper) GetStoreLTV(ctx sdk.Context, addr sdk.AccAddress) (sdk.Dec, error) {
// Fetch deposits and parse coin denoms // Fetch deposits and parse coin denoms
deposit, found := k.GetDeposit(ctx, addr) deposit, found := k.GetDeposit(ctx, addr)
if !found { if !found {
return sdk.ZeroDec(), false, nil return sdk.ZeroDec(), nil
} }
// Fetch borrow balances and parse coin denoms // Fetch borrow balances and parse coin denoms
borrow, found := k.GetBorrow(ctx, addr) borrow, found := k.GetBorrow(ctx, addr)
if !found { if !found {
return sdk.ZeroDec(), false, nil return sdk.ZeroDec(), nil
} }
return k.CalculateLtv(ctx, deposit, borrow) return k.CalculateLtv(ctx, deposit, borrow)
@ -313,11 +305,11 @@ func (k Keeper) GetStoreLTV(ctx sdk.Context, addr sdk.AccAddress) (sdk.Dec, bool
// CalculateLtv calculates the potential LTV given a user's deposits and borrows. // CalculateLtv calculates the potential LTV given a user's deposits and borrows.
// The boolean returned indicates if the LTV should be added to the store's LTV index. // The boolean returned indicates if the LTV should be added to the store's LTV index.
func (k Keeper) CalculateLtv(ctx sdk.Context, deposit types.Deposit, borrow types.Borrow) (sdk.Dec, bool, error) { func (k Keeper) CalculateLtv(ctx sdk.Context, deposit types.Deposit, borrow types.Borrow) (sdk.Dec, error) {
// Load required liquidation data for every deposit/borrow denom // Load required liquidation data for every deposit/borrow denom
liqMap, err := k.LoadLiquidationData(ctx, deposit, borrow) liqMap, err := k.LoadLiquidationData(ctx, deposit, borrow)
if err != nil { if err != nil {
return sdk.ZeroDec(), false, nil return sdk.ZeroDec(), nil
} }
// Build valuation map to hold deposit coin USD valuations // Build valuation map to hold deposit coin USD valuations
@ -339,11 +331,11 @@ func (k Keeper) CalculateLtv(ctx sdk.Context, deposit types.Deposit, borrow type
// User doesn't have any deposits, catch divide by 0 error // User doesn't have any deposits, catch divide by 0 error
sumDeposits := depositCoinValues.Sum() sumDeposits := depositCoinValues.Sum()
if sumDeposits.Equal(sdk.ZeroDec()) { if sumDeposits.Equal(sdk.ZeroDec()) {
return sdk.ZeroDec(), false, nil return sdk.ZeroDec(), nil
} }
// Loan-to-Value ratio // Loan-to-Value ratio
return borrowCoinValues.Sum().Quo(sumDeposits), true, nil return borrowCoinValues.Sum().Quo(sumDeposits), nil
} }
// LoadLiquidationData returns liquidation data, deposit, borrow // LoadLiquidationData returns liquidation data, deposit, borrow

View File

@ -1358,9 +1358,8 @@ func (suite *KeeperTestSuite) TestKeeperLiquidation() {
suite.Require().True(foundDepositBefore) suite.Require().True(foundDepositBefore)
// Attempt to liquidate // Attempt to liquidate
liquidated, err := suite.keeper.AttemptKeeperLiquidation(liqCtx, tc.args.keeper, tc.args.borrower) err = suite.keeper.AttemptKeeperLiquidation(liqCtx, tc.args.keeper, tc.args.borrower)
if tc.errArgs.expectPass { if tc.errArgs.expectPass {
suite.Require().True(liquidated)
suite.Require().NoError(err) suite.Require().NoError(err)
// Check borrow does not exist after liquidation // Check borrow does not exist after liquidation
@ -1383,7 +1382,6 @@ func (suite *KeeperTestSuite) TestKeeperLiquidation() {
suite.Require().True(len(auctions) > 0) suite.Require().True(len(auctions) > 0)
suite.Require().Equal(tc.args.expectedAuctions, auctions) suite.Require().Equal(tc.args.expectedAuctions, auctions)
} else { } else {
suite.Require().False(liquidated)
suite.Require().Error(err) suite.Require().Error(err)
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains)) suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))

View File

@ -10,7 +10,7 @@ import (
// Repay borrowed funds // Repay borrowed funds
func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) error { func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) error {
// Get current stored LTV based on stored borrows/deposits // Get current stored LTV based on stored borrows/deposits
prevLtv, shouldRemoveIndex, err := k.GetStoreLTV(ctx, sender) prevLtv, err := k.GetStoreLTV(ctx, sender)
if err != nil { if err != nil {
return err return err
} }
@ -43,9 +43,18 @@ func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) e
// Update user's borrow in store // Update user's borrow in store
borrow.Amount = borrow.Amount.Sub(payment) borrow.Amount = borrow.Amount.Sub(payment)
k.SetBorrow(ctx, borrow)
k.UpdateItemInLtvIndex(ctx, prevLtv, shouldRemoveIndex, sender) // Calculate the new Loan-to-Value ratio of Deposit-to-Borrow
deposit, foundDeposit := k.GetDeposit(ctx, sender)
if !foundDeposit {
return types.ErrDepositNotFound
}
newLtv, err := k.CalculateLtv(ctx, deposit, borrow)
if err != nil {
return err
}
k.UpdateBorrowAndLtvIndex(ctx, borrow, newLtv, prevLtv)
// Update total borrowed amount // Update total borrowed amount
k.DecrementBorrowedCoins(ctx, payment) k.DecrementBorrowedCoins(ctx, payment)

View File

@ -10,7 +10,7 @@ import (
// Withdraw returns some or all of a deposit back to original depositor // Withdraw returns some or all of a deposit back to original depositor
func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coins) error { func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coins) error {
// Get current stored LTV based on stored borrows/deposits // Get current stored LTV based on stored borrows/deposits
prevLtv, shouldRemoveIndex, err := k.GetStoreLTV(ctx, depositor) prevLtv, err := k.GetStoreLTV(ctx, depositor)
if err != nil { if err != nil {
return err return err
} }
@ -27,18 +27,17 @@ func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Co
if err != nil { if err != nil {
return err return err
} }
proposedDeposit := types.NewDeposit(deposit.Depositor, deposit.Amount.Sub(amount), types.SupplyInterestFactors{})
borrow, found := k.GetBorrow(ctx, depositor) borrow, found := k.GetBorrow(ctx, depositor)
if !found { if !found {
borrow = types.Borrow{} borrow = types.Borrow{}
} }
proposedDeposit := types.NewDeposit(deposit.Depositor, deposit.Amount.Sub(amount), types.SupplyInterestFactors{})
valid, err := k.IsWithinValidLtvRange(ctx, proposedDeposit, borrow) valid, err := k.IsWithinValidLtvRange(ctx, proposedDeposit, borrow)
if err != nil { if err != nil {
return err return err
} }
if !valid { if !valid {
return sdkerrors.Wrapf(types.ErrInvalidWithdrawAmount, "proposed withdraw outside loan-to-value range") return sdkerrors.Wrapf(types.ErrInvalidWithdrawAmount, "proposed withdraw outside loan-to-value range")
} }
@ -60,9 +59,12 @@ func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Co
} }
deposit.Amount = deposit.Amount.Sub(amount) deposit.Amount = deposit.Amount.Sub(amount)
k.SetDeposit(ctx, deposit)
k.UpdateItemInLtvIndex(ctx, prevLtv, shouldRemoveIndex, depositor) newLtv, err := k.CalculateLtv(ctx, deposit, borrow)
if err != nil {
return err
}
k.UpdateDepositAndLtvIndex(ctx, deposit, newLtv, prevLtv)
// Update total supplied amount // Update total supplied amount
k.DecrementBorrowedCoins(ctx, amount) k.DecrementBorrowedCoins(ctx, amount)