fix: simplify collateral auction logic

This commit is contained in:
Kevin Davis 2020-05-11 18:35:16 -04:00
parent d489bacfac
commit 89b63a3cba
3 changed files with 34 additions and 145 deletions

View File

@ -1,8 +1,6 @@
package keeper package keeper
import ( import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/cdp/types" "github.com/kava-labs/kava/x/cdp/types"
@ -13,100 +11,15 @@ const (
dump = 100 dump = 100
) )
type partialDeposit struct {
Depositor sdk.AccAddress
Amount sdk.Coin
DebtShare sdk.Int
}
func newPartialDeposit(depositor sdk.AccAddress, amount sdk.Coin, ds sdk.Int) partialDeposit {
return partialDeposit{
Depositor: depositor,
Amount: amount,
DebtShare: ds,
}
}
type partialDeposits []partialDeposit
func (pd partialDeposits) SumCollateral() (sum sdk.Int) {
sum = sdk.ZeroInt()
for _, d := range pd {
sum = sum.Add(d.Amount.Amount)
}
return
}
func (pd partialDeposits) SumDebt() (sum sdk.Int) {
sum = sdk.ZeroInt()
for _, d := range pd {
sum = sum.Add(d.DebtShare)
}
return
}
// AuctionCollateral creates auctions from the input deposits which attempt to raise the corresponding amount of debt // AuctionCollateral creates auctions from the input deposits which attempt to raise the corresponding amount of debt
func (k Keeper) AuctionCollateral(ctx sdk.Context, deposits types.Deposits, debt sdk.Int, bidDenom string) error { func (k Keeper) AuctionCollateral(ctx sdk.Context, deposits types.Deposits, debt sdk.Int, bidDenom string) error {
auctionSize := k.getAuctionSize(ctx, deposits[0].Amount.Denom)
partialAuctionDeposits := partialDeposits{}
totalCollateral := deposits.SumCollateral()
for totalCollateral.GT(sdk.ZeroInt()) {
for i, dep := range deposits {
if dep.Amount.IsZero() {
continue
}
collateralAmount := dep.Amount.Amount
collateralDenom := dep.Amount.Denom
// create auctions from individual deposits that are larger than the auction size
debtChange, collateralChange, err := k.CreateAuctionsFromDeposit(ctx, dep, debt, totalCollateral, auctionSize, bidDenom)
if err != nil {
return err
}
fmt.Printf("AuctionCollateral:createFromD:loop%d:liquidator:%s\n", i, k.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc))
debt = debt.Sub(debtChange)
totalCollateral = totalCollateral.Sub(collateralChange)
dep.Amount = sdk.NewCoin(collateralDenom, collateralAmount.Sub(collateralChange))
collateralAmount = collateralAmount.Sub(collateralChange)
// if there is leftover collateral that is less than a lot
if !dep.Amount.IsZero() {
// figure out how much debt this deposit accounts for
// (depositCollateral / totalCollateral) * totalDebtFromCDP
debtCoveredByDeposit := collateralAmount.Mul(debt).Quo(totalCollateral) // integer division (ie rounds down)
// if adding this deposit to the other partial deposits is less than a lot
if (partialAuctionDeposits.SumCollateral().Add(collateralAmount)).LT(auctionSize) {
// append the deposit to the partial deposits and zero out the deposit
pd := newPartialDeposit(dep.Depositor, dep.Amount, debtCoveredByDeposit)
partialAuctionDeposits = append(partialAuctionDeposits, pd)
dep.Amount = sdk.NewCoin(collateralDenom, sdk.ZeroInt())
} else {
// if the sum of partial deposits now makes a lot
partialCollateral := sdk.NewCoin(collateralDenom, auctionSize.Sub(partialAuctionDeposits.SumCollateral()))
partialAmount := partialCollateral.Amount
partialDebt := partialAmount.Mul(debtCoveredByDeposit).Quo(collateralAmount) // integer division (ie rounds down)
// create a partial deposit from the deposit auctionSize := k.getAuctionSize(ctx, deposits[0].Amount.Denom)
partialDep := newPartialDeposit(dep.Depositor, partialCollateral, partialDebt) totalCollateral := deposits.SumCollateral()
// append it to the partial deposits for _, deposit := range deposits {
partialAuctionDeposits = append(partialAuctionDeposits, partialDep)
// create an auction from the partial deposits debtCoveredByDeposit := (sdk.NewDecFromInt(deposit.Amount.Amount).Quo(sdk.NewDecFromInt(totalCollateral))).Mul(sdk.NewDecFromInt(debt)).RoundInt()
debtChange, collateralChange, err := k.CreateAuctionFromPartialDeposits(ctx, partialAuctionDeposits, debt, totalCollateral, auctionSize, bidDenom) err := k.CreateAuctionsFromDeposit(ctx, deposit.Amount, deposit.Depositor, debtCoveredByDeposit, auctionSize, bidDenom)
if err != nil {
return err
}
fmt.Printf("AuctionCollateral:createFromPD:loop%d:liquidator:%s\n", i, k.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc))
debt = debt.Sub(debtChange)
totalCollateral = totalCollateral.Sub(collateralChange)
// reset partial deposits and update the deposit amount
partialAuctionDeposits = partialDeposits{}
dep.Amount = sdk.NewCoin(collateralDenom, collateralAmount.Sub(partialAmount))
}
}
deposits[i] = dep
totalCollateral = deposits.SumCollateral()
}
}
if partialAuctionDeposits.SumCollateral().GT(sdk.ZeroInt()) {
_, _, err := k.CreateAuctionFromPartialDeposits(ctx, partialAuctionDeposits, debt, totalCollateral, partialAuctionDeposits.SumCollateral(), bidDenom)
if err != nil { if err != nil {
return err return err
} }
@ -114,54 +27,37 @@ func (k Keeper) AuctionCollateral(ctx sdk.Context, deposits types.Deposits, debt
return nil return nil
} }
// CreateAuctionsFromDeposit creates auctions from the input deposit until there is less than auctionSize left on the deposit // CreateAuctionsFromDeposit creates auctions from the input deposit
func (k Keeper) CreateAuctionsFromDeposit( func (k Keeper) CreateAuctionsFromDeposit(
ctx sdk.Context, dep types.Deposit, debt sdk.Int, totalCollateral sdk.Int, auctionSize sdk.Int, ctx sdk.Context, collateral sdk.Coin, returnAddr sdk.AccAddress, debt, auctionSize sdk.Int,
principalDenom string) (debtChange sdk.Int, collateralChange sdk.Int, err error) { principalDenom string) (err error) {
debtChange = sdk.ZeroInt()
collateralChange = sdk.ZeroInt() amountToAuction := sdk.NewInt(collateral.Amount.Int64())
depositAmount := dep.Amount.Amount totalCollateralAmount := sdk.NewInt(collateral.Amount.Int64())
depositDenom := dep.Amount.Denom remainingDebt := sdk.NewInt(debt.Int64())
for depositAmount.GTE(auctionSize) { for amountToAuction.GT(sdk.ZeroInt()) {
// figure out how much debt is covered by one lots worth of collateral for amountToAuction.GT(auctionSize) {
depositDebtAmount := (sdk.NewDecFromInt(auctionSize).Quo(sdk.NewDecFromInt(totalCollateral))).Mul(sdk.NewDecFromInt(debt)).RoundInt() debtCoveredByAuction := (sdk.NewDecFromInt(auctionSize).Quo(sdk.NewDecFromInt(totalCollateralAmount))).Mul(sdk.NewDecFromInt(debt)).RoundInt()
penalty := k.ApplyLiquidationPenalty(ctx, depositDenom, depositDebtAmount) penalty := k.ApplyLiquidationPenalty(ctx, collateral.Denom, debtCoveredByAuction)
// start an auction for one lot, attempting to raise depositDebtAmount plus the liquidation penalty _, err := k.auctionKeeper.StartCollateralAuction(
_, err := k.auctionKeeper.StartCollateralAuction( ctx, types.LiquidatorMacc, sdk.NewCoin(collateral.Denom, auctionSize), sdk.NewCoin(principalDenom, debtCoveredByAuction.Add(penalty)), []sdk.AccAddress{returnAddr},
ctx, types.LiquidatorMacc, sdk.NewCoin(depositDenom, auctionSize), sdk.NewCoin(principalDenom, depositDebtAmount.Add(penalty)), []sdk.AccAddress{dep.Depositor}, []sdk.Int{auctionSize}, sdk.NewCoin(k.GetDebtDenom(ctx), debtCoveredByAuction))
[]sdk.Int{auctionSize}, sdk.NewCoin(k.GetDebtDenom(ctx), depositDebtAmount)) if err != nil {
if err != nil { return err
return sdk.ZeroInt(), sdk.ZeroInt(), err }
amountToAuction = amountToAuction.Sub(auctionSize)
remainingDebt = remainingDebt.Sub(debtCoveredByAuction)
} }
depositAmount = depositAmount.Sub(auctionSize) penalty := k.ApplyLiquidationPenalty(ctx, collateral.Denom, remainingDebt)
totalCollateral = totalCollateral.Sub(auctionSize) _, err := k.auctionKeeper.StartCollateralAuction(
debt = debt.Sub(depositDebtAmount) ctx, types.LiquidatorMacc, sdk.NewCoin(collateral.Denom, amountToAuction), sdk.NewCoin(principalDenom, remainingDebt.Add(penalty)), []sdk.AccAddress{returnAddr},
// subtract one lot's worth of debt from the total debt covered by this deposit []sdk.Int{amountToAuction}, sdk.NewCoin(k.GetDebtDenom(ctx), remainingDebt))
debtChange = debtChange.Add(depositDebtAmount) if err != nil {
collateralChange = collateralChange.Add(auctionSize) return err
}
amountToAuction = sdk.ZeroInt()
} }
return debtChange, collateralChange, nil return nil
}
// CreateAuctionFromPartialDeposits creates an auction from the input partial deposits
func (k Keeper) CreateAuctionFromPartialDeposits(ctx sdk.Context, partialDeps partialDeposits, debt sdk.Int, collateral sdk.Int, auctionSize sdk.Int, bidDenom string) (debtChange, collateralChange sdk.Int, err error) {
returnAddrs := []sdk.AccAddress{}
returnWeights := []sdk.Int{}
depositDenom := partialDeps[0].Amount.Denom
for _, pd := range partialDeps {
returnAddrs = append(returnAddrs, pd.Depositor)
returnWeights = append(returnWeights, pd.DebtShare)
}
penalty := k.ApplyLiquidationPenalty(ctx, depositDenom, partialDeps.SumDebt())
_, err = k.auctionKeeper.StartCollateralAuction(ctx, types.LiquidatorMacc, sdk.NewCoin(partialDeps[0].Amount.Denom, auctionSize), sdk.NewCoin(bidDenom, partialDeps.SumDebt().Add(penalty)), returnAddrs, returnWeights, sdk.NewCoin(k.GetDebtDenom(ctx), partialDeps.SumDebt()))
if err != nil {
return sdk.ZeroInt(), sdk.ZeroInt(), err
}
debtChange = partialDeps.SumDebt()
collateralChange = partialDeps.SumCollateral()
return debtChange, collateralChange, nil
} }
// NetSurplusAndDebt burns surplus and debt coins equal to the minimum of surplus and debt balances held by the liquidator module account // NetSurplusAndDebt burns surplus and debt coins equal to the minimum of surplus and debt balances held by the liquidator module account

View File

@ -32,7 +32,6 @@ func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) error {
if err != nil { if err != nil {
return err return err
} }
fmt.Printf("SeizeCollateral:liquidator: %s\n", k.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc))
// liquidate deposits and send collateral from cdp to liquidator // liquidate deposits and send collateral from cdp to liquidator
for _, dep := range deposits { for _, dep := range deposits {
@ -50,7 +49,6 @@ func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) error {
} }
k.DeleteDeposit(ctx, dep.CdpID, dep.Depositor) k.DeleteDeposit(ctx, dep.CdpID, dep.Depositor)
} }
fmt.Printf("SeizeCollateral:liquidator: %s\n", k.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc))
err = k.AuctionCollateral(ctx, deposits, debt, cdp.Principal.Denom) err = k.AuctionCollateral(ctx, deposits, debt, cdp.Principal.Denom)
if err != nil { if err != nil {
return err return err

View File

@ -2,7 +2,6 @@ package keeper_test
import ( import (
"errors" "errors"
"fmt"
"math/rand" "math/rand"
"testing" "testing"
"time" "time"
@ -167,10 +166,6 @@ func (suite *SeizeTestSuite) TestSeizeCollateralMultiDeposit() {
p := cdp.Principal.Amount p := cdp.Principal.Amount
cl := cdp.Collateral.Amount cl := cdp.Collateral.Amount
tpb := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx") tpb := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx")
fmt.Printf("%s\n", cdp)
fmt.Printf("%s\n", deposits)
fmt.Printf("cdpaccount: %s\n", suite.app.GetSupplyKeeper().GetModuleAccount(suite.ctx, types.ModuleName))
fmt.Printf("liquidator: %s\n", suite.app.GetSupplyKeeper().GetModuleAccount(suite.ctx, types.LiquidatorMacc))
err = suite.keeper.SeizeCollateral(suite.ctx, cdp) err = suite.keeper.SeizeCollateral(suite.ctx, cdp)
suite.NoError(err) suite.NoError(err)
tpa := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx") tpa := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx")