mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-24 23:35:19 +00:00
Refactor CreateAuctionsFromDeposit (#589)
* refactor CreateAuctionsFromDeposit to complete exhibits 7 & 8 by removing auction slices and simplify usage of largest remainder method
This commit is contained in:
parent
db2b237e1d
commit
f0e73e4aa8
@ -30,36 +30,80 @@ func (k Keeper) AuctionCollateral(ctx sdk.Context, deposits types.Deposits, debt
|
|||||||
// CreateAuctionsFromDeposit creates auctions from the input deposit
|
// CreateAuctionsFromDeposit creates auctions from the input deposit
|
||||||
func (k Keeper) CreateAuctionsFromDeposit(
|
func (k Keeper) CreateAuctionsFromDeposit(
|
||||||
ctx sdk.Context, collateral sdk.Coin, returnAddr sdk.AccAddress, debt, auctionSize sdk.Int,
|
ctx sdk.Context, collateral sdk.Coin, returnAddr sdk.AccAddress, debt, auctionSize sdk.Int,
|
||||||
principalDenom string) (err error) {
|
principalDenom string) error {
|
||||||
|
|
||||||
// the number of auctions to start with lot = auctionSize
|
// number of auctions of auctionSize
|
||||||
wholeAuctions := collateral.Amount.Quo(auctionSize)
|
numberOfAuctions := collateral.Amount.Quo(auctionSize)
|
||||||
// remaining collateral (< lot) to auction
|
debtPerAuction := debt.Mul(auctionSize).Quo(collateral.Amount)
|
||||||
partialAuctionAmount := collateral.Amount.Mod(auctionSize)
|
|
||||||
auctionLots := []sdk.Int{}
|
|
||||||
|
|
||||||
for i := int64(0); i < wholeAuctions.Int64(); i++ {
|
// last auction for remaining collateral (collateral < auctionSize)
|
||||||
auctionLots = append(auctionLots, 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())
|
||||||
}
|
}
|
||||||
if partialAuctionAmount.IsPositive() {
|
|
||||||
auctionLots = append(auctionLots, partialAuctionAmount)
|
|
||||||
}
|
|
||||||
// use the auction lots as weights to split the debt into buckets,
|
|
||||||
// where each bucket represents how much debt that auction will attempt to cover
|
|
||||||
debtAmounts := splitIntIntoWeightedBuckets(debt, auctionLots)
|
|
||||||
debtDenom := k.GetDebtDenom(ctx)
|
debtDenom := k.GetDebtDenom(ctx)
|
||||||
for i, debtAmount := range debtAmounts {
|
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, collateral.Denom, debtAmount)
|
penalty := k.ApplyLiquidationPenalty(ctx, collateral.Denom, debtAmount)
|
||||||
|
|
||||||
_, err := k.auctionKeeper.StartCollateralAuction(
|
_, err := k.auctionKeeper.StartCollateralAuction(
|
||||||
ctx, types.LiquidatorMacc, sdk.NewCoin(collateral.Denom, auctionLots[i]),
|
ctx, types.LiquidatorMacc, sdk.NewCoin(collateral.Denom, auctionSize),
|
||||||
sdk.NewCoin(principalDenom, debtAmount.Add(penalty)), []sdk.AccAddress{returnAddr},
|
sdk.NewCoin(principalDenom, debtAmount.Add(penalty)), []sdk.AccAddress{returnAddr},
|
||||||
[]sdk.Int{auctionLots[i]}, sdk.NewCoin(debtDenom, debtAmount),
|
[]sdk.Int{auctionSize}, sdk.NewCoin(debtDenom, debtAmount),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// skip last auction if there is no collateral left to auction
|
||||||
|
if !lastAuctionCollateral.IsPositive() {
|
||||||
return nil
|
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, collateral.Denom, lastAuctionDebt)
|
||||||
|
|
||||||
|
_, err := k.auctionKeeper.StartCollateralAuction(
|
||||||
|
ctx, types.LiquidatorMacc, sdk.NewCoin(collateral.Denom, lastAuctionCollateral),
|
||||||
|
sdk.NewCoin(principalDenom, lastAuctionDebt.Add(penalty)), []sdk.AccAddress{returnAddr},
|
||||||
|
[]sdk.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
|
// NetSurplusAndDebt burns surplus and debt coins equal to the minimum of surplus and debt balances held by the liquidator module account
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
package keeper
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// splitIntIntoWeightedBuckets divides an initial +ve integer among several buckets in proportion to the buckets' weights
|
|
||||||
// It uses the largest remainder method: https://en.wikipedia.org/wiki/Largest_remainder_method
|
|
||||||
// See also: https://stackoverflow.com/questions/13483430/how-to-make-rounded-percentages-add-up-to-100
|
|
||||||
// note - copied from auction, tests are located there.
|
|
||||||
func splitIntIntoWeightedBuckets(amount sdk.Int, buckets []sdk.Int) []sdk.Int {
|
|
||||||
// Limit input to +ve numbers as algorithm hasn't been scoped to work with -ve numbers.
|
|
||||||
if amount.IsNegative() {
|
|
||||||
panic("negative amount")
|
|
||||||
}
|
|
||||||
if len(buckets) < 1 {
|
|
||||||
panic("no buckets")
|
|
||||||
}
|
|
||||||
for _, bucket := range buckets {
|
|
||||||
if bucket.IsNegative() {
|
|
||||||
panic("negative bucket")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1) Split the amount by weights, recording whole number part and remainder
|
|
||||||
|
|
||||||
totalWeights := totalInts(buckets...)
|
|
||||||
if !totalWeights.IsPositive() {
|
|
||||||
panic("total weights must sum to > 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
quotients := make([]quoRem, len(buckets))
|
|
||||||
for i := range buckets {
|
|
||||||
// amount * ( weight/total_weight )
|
|
||||||
q := amount.Mul(buckets[i]).Quo(totalWeights)
|
|
||||||
r := amount.Mul(buckets[i]).Mod(totalWeights)
|
|
||||||
quotients[i] = quoRem{index: i, quo: q, rem: r}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) Calculate total left over from remainders, and apportion it to buckets with the highest remainder (to minimize error)
|
|
||||||
|
|
||||||
// sort by decreasing remainder order
|
|
||||||
sort.Slice(quotients, func(i, j int) bool {
|
|
||||||
return quotients[i].rem.GT(quotients[j].rem)
|
|
||||||
})
|
|
||||||
|
|
||||||
// calculate total left over from remainders
|
|
||||||
allocated := sdk.ZeroInt()
|
|
||||||
for _, qr := range quotients {
|
|
||||||
allocated = allocated.Add(qr.quo)
|
|
||||||
}
|
|
||||||
leftToAllocate := amount.Sub(allocated)
|
|
||||||
|
|
||||||
// apportion according to largest remainder
|
|
||||||
results := make([]sdk.Int, len(quotients))
|
|
||||||
for _, qr := range quotients {
|
|
||||||
results[qr.index] = qr.quo
|
|
||||||
if !leftToAllocate.IsZero() {
|
|
||||||
results[qr.index] = results[qr.index].Add(sdk.OneInt())
|
|
||||||
leftToAllocate = leftToAllocate.Sub(sdk.OneInt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
type quoRem struct {
|
|
||||||
index int
|
|
||||||
quo sdk.Int
|
|
||||||
rem sdk.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
// totalInts adds together sdk.Ints
|
|
||||||
func totalInts(is ...sdk.Int) sdk.Int {
|
|
||||||
total := sdk.ZeroInt()
|
|
||||||
for _, i := range is {
|
|
||||||
total = total.Add(i)
|
|
||||||
}
|
|
||||||
return total
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user