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) {
_, err := k.AttemptKeeperLiquidation(ctx, msg.Keeper, msg.Borrower)
err := k.AttemptKeeperLiquidation(ctx, msg.Keeper, msg.Borrower)
if err != nil {
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
prevLtv, shouldRemoveIndex, err := k.GetStoreLTV(ctx, borrower)
prevLtv, err := k.GetStoreLTV(ctx, borrower)
if err != nil {
return err
}
@ -85,11 +85,20 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.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)
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
// 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
prevLtv, shouldRemoveIndex, err := k.GetStoreLTV(ctx, depositor)
prevLtv, err := k.GetStoreLTV(ctx, depositor)
if err != nil {
return err
}
@ -87,12 +87,17 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coi
} else {
amount = coins
}
// Update the depositer's amount and supply interest factors in the store
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
// 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)
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 !errors.Is(err, types.ErrBorrowNotLiquidatable) {
if !errors.Is(err, types.ErrBorrowNotLiquidatable) && !errors.Is(err, types.ErrBorrowNotFound) {
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
func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress, borrower sdk.AccAddress) (bool, error) {
prevLtv, shouldInsertIndex, err := k.GetStoreLTV(ctx, borrower)
func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress, borrower sdk.AccAddress) error {
prevLtv, err := k.GetStoreLTV(ctx, borrower)
if err != nil {
return false, err
return err
}
k.SyncBorrowInterest(ctx, borrower)
k.UpdateItemInLtvIndex(ctx, prevLtv, shouldInsertIndex, borrower)
// Fetch deposits and parse coin denoms
deposit, found := k.GetDeposit(ctx, borrower)
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)
if !found {
return false, types.ErrBorrowNotFound
return types.ErrBorrowNotFound
}
isWithinRange, err := k.IsWithinValidLtvRange(ctx, deposit, borrow)
if err != nil {
return false, err
return err
}
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
@ -68,19 +64,11 @@ func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress,
depositDenoms := getDenoms(deposit.Amount)
err = k.SeizeDeposits(ctx, keeper, deposit, borrow, depositDenoms, borrowDenoms)
if err != nil {
return false, err
return err
}
currLtv, _, err := k.GetStoreLTV(ctx, borrower)
if err != nil {
return false, err
}
k.RemoveFromLtvIndex(ctx, currLtv, borrower)
k.DeleteBorrow(ctx, borrow)
k.DeleteDeposit(ctx, deposit)
return true, err
k.DeleteDepositBorrowAndLtvIndex(ctx, deposit, borrow, prevLtv)
return nil
}
// 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
}
// UpdateItemInLtvIndex updates the key a borrower's address is stored under in the LTV index
func (k Keeper) UpdateItemInLtvIndex(ctx sdk.Context, prevLtv sdk.Dec,
shouldRemoveIndex bool, borrower sdk.AccAddress) error {
currLtv, shouldInsertIndex, err := k.GetStoreLTV(ctx, borrower)
if err != nil {
return err
}
// UpdateBorrowAndLtvIndex updates a borrow and its LTV index value in the store
func (k Keeper) UpdateBorrowAndLtvIndex(ctx sdk.Context, borrow types.Borrow, newLtv, oldLtv sdk.Dec) {
k.RemoveFromLtvIndex(ctx, oldLtv, borrow.Borrower)
k.SetBorrow(ctx, borrow)
k.InsertIntoLtvIndex(ctx, newLtv, borrow.Borrower)
}
if shouldRemoveIndex {
k.RemoveFromLtvIndex(ctx, prevLtv, borrower)
}
if shouldInsertIndex {
k.InsertIntoLtvIndex(ctx, currLtv, borrower)
}
return nil
// UpdateDepositAndLtvIndex updates a deposit and its LTV index value in the store
func (k Keeper) UpdateDepositAndLtvIndex(ctx sdk.Context, deposit types.Deposit, newLtv, oldLtv sdk.Dec) {
k.RemoveFromLtvIndex(ctx, oldLtv, deposit.Depositor)
k.SetDeposit(ctx, deposit)
k.InsertIntoLtvIndex(ctx, newLtv, deposit.Depositor)
}
// 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
// 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
deposit, found := k.GetDeposit(ctx, addr)
if !found {
return sdk.ZeroDec(), false, nil
return sdk.ZeroDec(), nil
}
// Fetch borrow balances and parse coin denoms
borrow, found := k.GetBorrow(ctx, addr)
if !found {
return sdk.ZeroDec(), false, nil
return sdk.ZeroDec(), nil
}
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.
// 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
liqMap, err := k.LoadLiquidationData(ctx, deposit, borrow)
if err != nil {
return sdk.ZeroDec(), false, nil
return sdk.ZeroDec(), nil
}
// 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
sumDeposits := depositCoinValues.Sum()
if sumDeposits.Equal(sdk.ZeroDec()) {
return sdk.ZeroDec(), false, nil
return sdk.ZeroDec(), nil
}
// Loan-to-Value ratio
return borrowCoinValues.Sum().Quo(sumDeposits), true, nil
return borrowCoinValues.Sum().Quo(sumDeposits), nil
}
// LoadLiquidationData returns liquidation data, deposit, borrow

View File

@ -1358,9 +1358,8 @@ func (suite *KeeperTestSuite) TestKeeperLiquidation() {
suite.Require().True(foundDepositBefore)
// 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 {
suite.Require().True(liquidated)
suite.Require().NoError(err)
// Check borrow does not exist after liquidation
@ -1383,7 +1382,6 @@ func (suite *KeeperTestSuite) TestKeeperLiquidation() {
suite.Require().True(len(auctions) > 0)
suite.Require().Equal(tc.args.expectedAuctions, auctions)
} else {
suite.Require().False(liquidated)
suite.Require().Error(err)
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))

View File

@ -10,7 +10,7 @@ import (
// Repay borrowed funds
func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) error {
// 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 {
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
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
k.DecrementBorrowedCoins(ctx, payment)

View File

@ -10,7 +10,7 @@ import (
// 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 {
// 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 {
return err
}
@ -27,18 +27,17 @@ func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Co
if err != nil {
return err
}
proposedDeposit := types.NewDeposit(deposit.Depositor, deposit.Amount.Sub(amount), types.SupplyInterestFactors{})
borrow, found := k.GetBorrow(ctx, depositor)
if !found {
borrow = types.Borrow{}
}
proposedDeposit := types.NewDeposit(deposit.Depositor, deposit.Amount.Sub(amount), types.SupplyInterestFactors{})
valid, err := k.IsWithinValidLtvRange(ctx, proposedDeposit, borrow)
if err != nil {
return err
}
if !valid {
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)
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
k.DecrementBorrowedCoins(ctx, amount)