mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-11-14 12:05:18 +00:00
235 lines
9.9 KiB
Go
235 lines
9.9 KiB
Go
package keeper
|
|
|
|
import (
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
"github.com/kava-labs/kava/x/cdp/types"
|
|
)
|
|
|
|
const (
|
|
// factor for setting the initial value of gov tokens to sell at debt auctions -- assuming stable token is ~1 usd, this starts the auction with a price of $0.01 KAVA
|
|
dump = 100
|
|
)
|
|
|
|
type partialDeposit struct {
|
|
Depositor sdk.AccAddress
|
|
Amount sdk.Coins
|
|
DebtShare sdk.Int
|
|
}
|
|
|
|
func newPartialDeposit(depositor sdk.AccAddress, amount sdk.Coins, 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[0].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
|
|
func (k Keeper) AuctionCollateral(ctx sdk.Context, deposits types.Deposits, debt sdk.Int, bidDenom string) sdk.Error {
|
|
auctionSize := k.getAuctionSize(ctx, deposits[0].Amount[0].Denom)
|
|
partialAuctionDeposits := partialDeposits{}
|
|
totalCollateral := deposits.SumCollateral()
|
|
for totalCollateral.GT(sdk.ZeroInt()) {
|
|
for i, dep := range deposits {
|
|
if dep.Amount.IsZero() {
|
|
continue
|
|
}
|
|
collateralAmount := dep.Amount[0].Amount
|
|
collateralDenom := dep.Amount[0].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
|
|
}
|
|
debt = debt.Sub(debtChange)
|
|
totalCollateral = totalCollateral.Sub(collateralChange)
|
|
dep.Amount = sdk.NewCoins(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.Quo(totalCollateral)).Mul(debt)
|
|
// 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.NewCoins(sdk.NewCoin(collateralDenom, sdk.ZeroInt()))
|
|
} else {
|
|
// if the sum of partial deposits now makes a lot
|
|
partialCollateral := sdk.NewCoins(sdk.NewCoin(collateralDenom, auctionSize.Sub(partialAuctionDeposits.SumCollateral())))
|
|
partialAmount := partialCollateral[0].Amount
|
|
partialDebt := (partialAmount.Quo(collateralAmount)).Mul(debtCoveredByDeposit)
|
|
|
|
// create a partial deposit from the deposit
|
|
partialDep := newPartialDeposit(dep.Depositor, partialCollateral, partialDebt)
|
|
// append it to the partial deposits
|
|
partialAuctionDeposits = append(partialAuctionDeposits, partialDep)
|
|
// create an auction from the partial deposits
|
|
debtChange, collateralChange, err := k.CreateAuctionFromPartialDeposits(ctx, partialAuctionDeposits, debt, totalCollateral, auctionSize, bidDenom)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
debt = debt.Sub(debtChange)
|
|
totalCollateral = totalCollateral.Sub(collateralChange)
|
|
// reset partial deposits and update the deposit amount
|
|
partialAuctionDeposits = partialDeposits{}
|
|
dep.Amount = sdk.NewCoins(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 {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CreateAuctionsFromDeposit creates auctions from the input deposit until there is less than auctionSize left on the deposit
|
|
func (k Keeper) CreateAuctionsFromDeposit(ctx sdk.Context, dep types.Deposit, debt sdk.Int, totalCollateral sdk.Int, auctionSize sdk.Int, principalDenom string) (debtChange sdk.Int, collateralChange sdk.Int, err sdk.Error) {
|
|
debtChange = sdk.ZeroInt()
|
|
collateralChange = sdk.ZeroInt()
|
|
depositAmount := dep.Amount[0].Amount
|
|
depositDenom := dep.Amount[0].Denom
|
|
for depositAmount.GTE(auctionSize) {
|
|
// figure out how much debt is covered by one lots worth of collateral
|
|
depositDebtAmount := (sdk.NewDecFromInt(auctionSize).Quo(sdk.NewDecFromInt(totalCollateral))).Mul(sdk.NewDecFromInt(debt)).RoundInt()
|
|
penalty := k.ApplyLiquidationPenalty(ctx, depositDenom, depositDebtAmount)
|
|
// start an auction for one lot, attempting to raise depositDebtAmount plus the liquidation penalty
|
|
_, err := k.auctionKeeper.StartCollateralAuction(
|
|
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), depositDebtAmount))
|
|
if err != nil {
|
|
return sdk.ZeroInt(), sdk.ZeroInt(), err
|
|
}
|
|
depositAmount = depositAmount.Sub(auctionSize)
|
|
totalCollateral = totalCollateral.Sub(auctionSize)
|
|
debt = debt.Sub(depositDebtAmount)
|
|
// subtract one lot's worth of debt from the total debt covered by this deposit
|
|
debtChange = debtChange.Add(depositDebtAmount)
|
|
collateralChange = collateralChange.Add(auctionSize)
|
|
|
|
}
|
|
return debtChange, collateralChange, 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 sdk.Error) {
|
|
|
|
returnAddrs := []sdk.AccAddress{}
|
|
returnWeights := []sdk.Int{}
|
|
depositDenom := partialDeps[0].Amount[0].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[0].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
|
|
// for example, if there is 1000 debt and 100 surplus, 100 surplus and 100 debt are burned, netting to 900 debt
|
|
func (k Keeper) NetSurplusAndDebt(ctx sdk.Context) sdk.Error {
|
|
totalSurplus := k.GetTotalSurplus(ctx, types.LiquidatorMacc)
|
|
debt := k.GetTotalDebt(ctx, types.LiquidatorMacc)
|
|
netAmount := sdk.MinInt(totalSurplus, debt)
|
|
if netAmount.IsZero() {
|
|
return nil
|
|
}
|
|
err := k.supplyKeeper.BurnCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(k.GetDebtDenom(ctx), netAmount)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for netAmount.GT(sdk.ZeroInt()) {
|
|
for _, dp := range k.GetParams(ctx).DebtParams {
|
|
balance := k.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc).GetCoins().AmountOf(dp.Denom)
|
|
if balance.LT(netAmount) {
|
|
err = k.supplyKeeper.BurnCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, balance)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
netAmount = netAmount.Sub(balance)
|
|
} else {
|
|
err = k.supplyKeeper.BurnCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, netAmount)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
netAmount = sdk.ZeroInt()
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetTotalSurplus returns the total amount of surplus tokens held by the liquidator module account
|
|
func (k Keeper) GetTotalSurplus(ctx sdk.Context, accountName string) sdk.Int {
|
|
acc := k.supplyKeeper.GetModuleAccount(ctx, accountName)
|
|
totalSurplus := sdk.ZeroInt()
|
|
for _, dp := range k.GetParams(ctx).DebtParams {
|
|
surplus := acc.GetCoins().AmountOf(dp.Denom)
|
|
totalSurplus = totalSurplus.Add(surplus)
|
|
}
|
|
return totalSurplus
|
|
}
|
|
|
|
// GetTotalDebt returns the total amount of debt tokens held by the liquidator module account
|
|
func (k Keeper) GetTotalDebt(ctx sdk.Context, accountName string) sdk.Int {
|
|
acc := k.supplyKeeper.GetModuleAccount(ctx, accountName)
|
|
debt := acc.GetCoins().AmountOf(k.GetDebtDenom(ctx))
|
|
return debt
|
|
}
|
|
|
|
// RunSurplusAndDebtAuctions nets the surplus and debt balances and then creates surplus or debt auctions if the remaining balance is above the auction threshold parameter
|
|
func (k Keeper) RunSurplusAndDebtAuctions(ctx sdk.Context) sdk.Error {
|
|
k.NetSurplusAndDebt(ctx)
|
|
remainingDebt := k.GetTotalDebt(ctx, types.LiquidatorMacc)
|
|
params := k.GetParams(ctx)
|
|
if remainingDebt.GTE(params.DebtAuctionThreshold) {
|
|
_, err := k.auctionKeeper.StartDebtAuction(ctx, types.LiquidatorMacc, sdk.NewCoin("usdx", remainingDebt), sdk.NewCoin(k.GetGovDenom(ctx), remainingDebt.Mul(sdk.NewInt(dump))), sdk.NewCoin(k.GetDebtDenom(ctx), remainingDebt))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
remainingSurplus := k.GetTotalSurplus(ctx, types.LiquidatorMacc)
|
|
if remainingSurplus.GTE(params.SurplusAuctionThreshold) {
|
|
for _, dp := range params.DebtParams {
|
|
surplusLot := k.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc).GetCoins().AmountOf(dp.Denom)
|
|
_, err := k.auctionKeeper.StartSurplusAuction(ctx, types.LiquidatorMacc, sdk.NewCoin(dp.Denom, surplusLot), k.GetGovDenom(ctx))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|