mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 10:07:26 +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
 | 
			
		||||
func (k Keeper) CreateAuctionsFromDeposit(
 | 
			
		||||
	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
 | 
			
		||||
	wholeAuctions := collateral.Amount.Quo(auctionSize)
 | 
			
		||||
	// remaining collateral (< lot) to auction
 | 
			
		||||
	partialAuctionAmount := collateral.Amount.Mod(auctionSize)
 | 
			
		||||
	auctionLots := []sdk.Int{}
 | 
			
		||||
	// number of auctions of auctionSize
 | 
			
		||||
	numberOfAuctions := collateral.Amount.Quo(auctionSize)
 | 
			
		||||
	debtPerAuction := debt.Mul(auctionSize).Quo(collateral.Amount)
 | 
			
		||||
 | 
			
		||||
	for i := int64(0); i < wholeAuctions.Int64(); i++ {
 | 
			
		||||
		auctionLots = append(auctionLots, auctionSize)
 | 
			
		||||
	// 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())
 | 
			
		||||
	}
 | 
			
		||||
	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)
 | 
			
		||||
	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)
 | 
			
		||||
 | 
			
		||||
		_, 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.Int{auctionLots[i]}, sdk.NewCoin(debtDenom, debtAmount),
 | 
			
		||||
			[]sdk.Int{auctionSize}, sdk.NewCoin(debtDenom, debtAmount),
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
 | 
			
		||||
	// 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, 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
 | 
			
		||||
 | 
			
		||||
@ -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