/
auctions.go
171 lines (142 loc) · 7.06 KB
/
auctions.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package keeper
import (
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/elysium-station/black/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 BLACK
dump = 100
)
// 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, collateralType string, debt sdkmath.Int, bidDenom string) error {
auctionSize := k.getAuctionSize(ctx, collateralType)
totalCollateral := deposits.SumCollateral()
for _, deposit := range deposits {
debtCoveredByDeposit := (sdk.NewDecFromInt(deposit.Amount.Amount).Quo(sdk.NewDecFromInt(totalCollateral))).Mul(sdk.NewDecFromInt(debt)).RoundInt()
if err := k.CreateAuctionsFromDeposit(ctx, deposit.Amount, collateralType, deposit.Depositor, debtCoveredByDeposit, auctionSize, bidDenom); err != nil {
return err
}
}
return nil
}
// CreateAuctionsFromDeposit creates auctions from the input deposit
func (k Keeper) CreateAuctionsFromDeposit(
ctx sdk.Context, collateral sdk.Coin, collateralType string, returnAddr sdk.AccAddress, debt, auctionSize sdkmath.Int,
principalDenom string,
) error {
// number of auctions of auctionSize
numberOfAuctions := collateral.Amount.Quo(auctionSize)
debtPerAuction := debt.Mul(auctionSize).Quo(collateral.Amount)
// last auction for remaining collateral (collateral < auctionSize)
lastAuctionCollateral := collateral.Amount.Mod(auctionSize)
lastAuctionDebt := debt.Mul(lastAuctionCollateral).Quo(collateral.Amount)
// amount of debt that has not been allocated due to
// rounding error (unallocated debt is less than numberOfAuctions + 1)
unallocatedDebt := debt.Sub(numberOfAuctions.Mul(debtPerAuction).Add(lastAuctionDebt))
// rounding error for whole and last auctions in units of collateral
// higher value means a larger truncation
wholeAuctionError := debt.Mul(auctionSize).Mod(collateral.Amount)
lastAuctionError := debt.Mul(lastAuctionCollateral).Mod(collateral.Amount)
// if last auction has larger rounding error, then allocate one debt to last auction first
// follows the largest remainder method https://en.wikipedia.org/wiki/Largest_remainder_method
if lastAuctionError.GT(wholeAuctionError) {
lastAuctionDebt = lastAuctionDebt.Add(sdk.OneInt())
unallocatedDebt = unallocatedDebt.Sub(sdk.OneInt())
}
debtDenom := k.GetDebtDenom(ctx)
numAuctions := numberOfAuctions.Int64()
// create whole auctions
for i := int64(0); i < numAuctions; i++ {
debtAmount := debtPerAuction
// distribute unallocated debt left over starting with first auction created
if unallocatedDebt.IsPositive() {
debtAmount = debtAmount.Add(sdk.OneInt())
unallocatedDebt = unallocatedDebt.Sub(sdk.OneInt())
}
penalty := k.ApplyLiquidationPenalty(ctx, collateralType, debtAmount)
_, err := k.auctionKeeper.StartCollateralAuction(
ctx, types.LiquidatorMacc, sdk.NewCoin(collateral.Denom, auctionSize),
sdk.NewCoin(principalDenom, debtAmount.Add(penalty)), []sdk.AccAddress{returnAddr},
[]sdkmath.Int{auctionSize}, sdk.NewCoin(debtDenom, debtAmount),
)
if err != nil {
return err
}
}
// skip last auction if there is no collateral left to auction
if !lastAuctionCollateral.IsPositive() {
return nil
}
// if the last auction had a larger rounding error than whole auctions,
// then unallocatedDebt will be zero since we will have already distributed
// all of the unallocated debt
if unallocatedDebt.IsPositive() {
lastAuctionDebt = lastAuctionDebt.Add(sdk.OneInt())
unallocatedDebt = unallocatedDebt.Sub(sdk.OneInt())
}
penalty := k.ApplyLiquidationPenalty(ctx, collateralType, lastAuctionDebt)
_, err := k.auctionKeeper.StartCollateralAuction(
ctx, types.LiquidatorMacc, sdk.NewCoin(collateral.Denom, lastAuctionCollateral),
sdk.NewCoin(principalDenom, lastAuctionDebt.Add(penalty)), []sdk.AccAddress{returnAddr},
[]sdkmath.Int{lastAuctionCollateral}, sdk.NewCoin(debtDenom, lastAuctionDebt),
)
return err
}
// 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) error {
totalSurplus := k.GetTotalSurplus(ctx, types.LiquidatorMacc)
debt := k.GetTotalDebt(ctx, types.LiquidatorMacc)
netAmount := sdk.MinInt(totalSurplus, debt)
if netAmount.IsZero() {
return nil
}
// burn debt coins equal to netAmount
err := k.bankKeeper.BurnCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(k.GetDebtDenom(ctx), netAmount)))
if err != nil {
return err
}
// burn stable coins equal to min(balance, netAmount)
dp := k.GetParams(ctx).DebtParam
liquidatorAcc := k.accountKeeper.GetModuleAccount(ctx, types.LiquidatorMacc)
balance := k.bankKeeper.GetBalance(ctx, liquidatorAcc.GetAddress(), dp.Denom).Amount
burnAmount := sdk.MinInt(balance, netAmount)
return k.bankKeeper.BurnCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, burnAmount)))
}
// GetTotalSurplus returns the total amount of surplus tokens held by the liquidator module account
func (k Keeper) GetTotalSurplus(ctx sdk.Context, accountName string) sdkmath.Int {
acc := k.accountKeeper.GetModuleAccount(ctx, accountName)
dp := k.GetParams(ctx).DebtParam
return k.bankKeeper.GetBalance(ctx, acc.GetAddress(), dp.Denom).Amount
}
// GetTotalDebt returns the total amount of debt tokens held by the liquidator module account
func (k Keeper) GetTotalDebt(ctx sdk.Context, accountName string) sdkmath.Int {
acc := k.accountKeeper.GetModuleAccount(ctx, accountName)
return k.bankKeeper.GetBalance(ctx, acc.GetAddress(), k.GetDebtDenom(ctx)).Amount
}
// 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) error {
if err := k.NetSurplusAndDebt(ctx); err != nil {
return err
}
remainingDebt := k.GetTotalDebt(ctx, types.LiquidatorMacc)
params := k.GetParams(ctx)
if remainingDebt.GTE(params.DebtAuctionThreshold) {
debtLot := sdk.NewCoin(k.GetDebtDenom(ctx), params.DebtAuctionLot)
bidCoin := sdk.NewCoin(params.DebtParam.Denom, debtLot.Amount)
initialLot := sdk.NewCoin(k.GetGovDenom(ctx), debtLot.Amount.Mul(sdkmath.NewInt(dump)))
_, err := k.auctionKeeper.StartDebtAuction(ctx, types.LiquidatorMacc, bidCoin, initialLot, debtLot)
if err != nil {
return err
}
}
liquidatorAcc := k.accountKeeper.GetModuleAccount(ctx, types.LiquidatorMacc)
surplus := k.bankKeeper.GetBalance(ctx, liquidatorAcc.GetAddress(), params.DebtParam.Denom).Amount
if !surplus.GTE(params.SurplusAuctionThreshold) {
return nil
}
surplusLot := sdk.NewCoin(params.DebtParam.Denom, sdk.MinInt(params.SurplusAuctionLot, surplus))
_, err := k.auctionKeeper.StartSurplusAuction(ctx, types.LiquidatorMacc, surplusLot, k.GetGovDenom(ctx))
return err
}