mirror of
https://github.com/0glabs/0g-chain.git
synced 2025-01-12 08:15:18 +00:00
Minor audit fixes (#540)
* use mod and quo to calculate how many auctions to start * check both return values in getDenomPrefix * split auctions using weighted buckets
This commit is contained in:
parent
aebb3093ff
commit
c049aad495
@ -32,32 +32,33 @@ 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) (err error) {
|
||||||
|
|
||||||
amountToAuction := collateral.Amount
|
// the number of auctions to start with lot = auctionSize
|
||||||
totalCollateralAmount := collateral.Amount
|
wholeAuctions := collateral.Amount.Quo(auctionSize)
|
||||||
remainingDebt := debt
|
// remaining collateral (< lot) to auction
|
||||||
if !amountToAuction.IsPositive() {
|
partialAuctionAmount := collateral.Amount.Mod(auctionSize)
|
||||||
return nil
|
auctionLots := []sdk.Int{}
|
||||||
}
|
|
||||||
for amountToAuction.GT(auctionSize) {
|
|
||||||
debtCoveredByAuction := (sdk.NewDecFromInt(auctionSize).Quo(sdk.NewDecFromInt(totalCollateralAmount))).Mul(sdk.NewDecFromInt(debt)).RoundInt()
|
|
||||||
penalty := k.ApplyLiquidationPenalty(ctx, collateral.Denom, debtCoveredByAuction)
|
|
||||||
_, err := k.auctionKeeper.StartCollateralAuction(
|
|
||||||
ctx, types.LiquidatorMacc, sdk.NewCoin(collateral.Denom, auctionSize), sdk.NewCoin(principalDenom, debtCoveredByAuction.Add(penalty)), []sdk.AccAddress{returnAddr},
|
|
||||||
[]sdk.Int{auctionSize}, sdk.NewCoin(k.GetDebtDenom(ctx), debtCoveredByAuction))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
amountToAuction = amountToAuction.Sub(auctionSize)
|
|
||||||
remainingDebt = remainingDebt.Sub(debtCoveredByAuction)
|
|
||||||
}
|
|
||||||
penalty := k.ApplyLiquidationPenalty(ctx, collateral.Denom, remainingDebt)
|
|
||||||
_, err = k.auctionKeeper.StartCollateralAuction(
|
|
||||||
ctx, types.LiquidatorMacc, sdk.NewCoin(collateral.Denom, amountToAuction), sdk.NewCoin(principalDenom, remainingDebt.Add(penalty)), []sdk.AccAddress{returnAddr},
|
|
||||||
[]sdk.Int{amountToAuction}, sdk.NewCoin(k.GetDebtDenom(ctx), remainingDebt))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for i := int64(0); i < wholeAuctions.Int64(); i++ {
|
||||||
|
auctionLots = append(auctionLots, auctionSize)
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
penalty := k.ApplyLiquidationPenalty(ctx, collateral.Denom, debtAmount)
|
||||||
|
_, err := k.auctionKeeper.StartCollateralAuction(
|
||||||
|
ctx, types.LiquidatorMacc, sdk.NewCoin(collateral.Denom, auctionLots[i]),
|
||||||
|
sdk.NewCoin(principalDenom, debtAmount.Add(penalty)), []sdk.AccAddress{returnAddr},
|
||||||
|
[]sdk.Int{auctionLots[i]}, sdk.NewCoin(debtDenom, debtAmount),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
"github.com/tendermint/tendermint/crypto"
|
||||||
tmtime "github.com/tendermint/tendermint/types/time"
|
tmtime "github.com/tendermint/tendermint/types/time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,14 +23,18 @@ type AuctionTestSuite struct {
|
|||||||
keeper keeper.Keeper
|
keeper keeper.Keeper
|
||||||
app app.TestApp
|
app app.TestApp
|
||||||
ctx sdk.Context
|
ctx sdk.Context
|
||||||
|
addrs []sdk.AccAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AuctionTestSuite) SetupTest() {
|
func (suite *AuctionTestSuite) SetupTest() {
|
||||||
config := sdk.GetConfig()
|
config := sdk.GetConfig()
|
||||||
app.SetBech32AddressPrefixes(config)
|
app.SetBech32AddressPrefixes(config)
|
||||||
tApp := app.NewTestApp()
|
tApp := app.NewTestApp()
|
||||||
|
taddr := sdk.AccAddress(crypto.AddressHash([]byte("KavaTestUser1")))
|
||||||
|
authGS := app.NewAuthGenState([]sdk.AccAddress{taddr}, []sdk.Coins{cs(c("usdx", 21000000000))})
|
||||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
||||||
tApp.InitializeFromGenesisStates(
|
tApp.InitializeFromGenesisStates(
|
||||||
|
authGS,
|
||||||
NewPricefeedGenStateMulti(),
|
NewPricefeedGenStateMulti(),
|
||||||
NewCDPGenStateMulti(),
|
NewCDPGenStateMulti(),
|
||||||
)
|
)
|
||||||
@ -37,6 +42,7 @@ func (suite *AuctionTestSuite) SetupTest() {
|
|||||||
suite.app = tApp
|
suite.app = tApp
|
||||||
suite.ctx = ctx
|
suite.ctx = ctx
|
||||||
suite.keeper = keeper
|
suite.keeper = keeper
|
||||||
|
suite.addrs = []sdk.AccAddress{taddr}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +57,15 @@ func (suite *AuctionTestSuite) TestNetDebtSurplus() {
|
|||||||
suite.Equal(cs(c("debt", 90)), acc.GetCoins())
|
suite.Equal(cs(c("debt", 90)), acc.GetCoins())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *AuctionTestSuite) TestCollateralAuction() {
|
||||||
|
sk := suite.app.GetSupplyKeeper()
|
||||||
|
err := sk.MintCoins(suite.ctx, types.LiquidatorMacc, cs(c("debt", 21000000000), c("bnb", 190000000000)))
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
testDeposit := types.NewDeposit(1, suite.addrs[0], c("bnb", 190000000000))
|
||||||
|
err = suite.keeper.AuctionCollateral(suite.ctx, types.Deposits{testDeposit}, i(21000000000), "usdx")
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *AuctionTestSuite) TestSurplusAuction() {
|
func (suite *AuctionTestSuite) TestSurplusAuction() {
|
||||||
sk := suite.app.GetSupplyKeeper()
|
sk := suite.app.GetSupplyKeeper()
|
||||||
err := sk.MintCoins(suite.ctx, types.LiquidatorMacc, cs(c("usdx", 600000000000)))
|
err := sk.MintCoins(suite.ctx, types.LiquidatorMacc, cs(c("usdx", 600000000000)))
|
||||||
|
@ -302,14 +302,20 @@ func (k Keeper) RemoveCdpOwnerIndex(ctx sdk.Context, cdp types.CDP) {
|
|||||||
// IndexCdpByCollateralRatio sets the cdp id in the store, indexed by the collateral type and collateral to debt ratio
|
// IndexCdpByCollateralRatio sets the cdp id in the store, indexed by the collateral type and collateral to debt ratio
|
||||||
func (k Keeper) IndexCdpByCollateralRatio(ctx sdk.Context, denom string, id uint64, collateralRatio sdk.Dec) {
|
func (k Keeper) IndexCdpByCollateralRatio(ctx sdk.Context, denom string, id uint64, collateralRatio sdk.Dec) {
|
||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.CollateralRatioIndexPrefix)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.CollateralRatioIndexPrefix)
|
||||||
db, _ := k.GetDenomPrefix(ctx, denom)
|
db, found := k.GetDenomPrefix(ctx, denom)
|
||||||
|
if !found {
|
||||||
|
panic(fmt.Sprintf("denom %s prefix not found", denom))
|
||||||
|
}
|
||||||
store.Set(types.CollateralRatioKey(db, id, collateralRatio), types.GetCdpIDBytes(id))
|
store.Set(types.CollateralRatioKey(db, id, collateralRatio), types.GetCdpIDBytes(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveCdpCollateralRatioIndex deletes the cdp id from the store's index of cdps by collateral type and collateral to debt ratio
|
// RemoveCdpCollateralRatioIndex deletes the cdp id from the store's index of cdps by collateral type and collateral to debt ratio
|
||||||
func (k Keeper) RemoveCdpCollateralRatioIndex(ctx sdk.Context, denom string, id uint64, collateralRatio sdk.Dec) {
|
func (k Keeper) RemoveCdpCollateralRatioIndex(ctx sdk.Context, denom string, id uint64, collateralRatio sdk.Dec) {
|
||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.CollateralRatioIndexPrefix)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.CollateralRatioIndexPrefix)
|
||||||
db, _ := k.GetDenomPrefix(ctx, denom)
|
db, found := k.GetDenomPrefix(ctx, denom)
|
||||||
|
if !found {
|
||||||
|
panic(fmt.Sprintf("denom %s prefix not found", denom))
|
||||||
|
}
|
||||||
store.Delete(types.CollateralRatioKey(db, id, collateralRatio))
|
store.Delete(types.CollateralRatioKey(db, id, collateralRatio))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +83,7 @@ func NewPricefeedGenStateMulti() app.GenesisState {
|
|||||||
Markets: []pricefeed.Market{
|
Markets: []pricefeed.Market{
|
||||||
{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
||||||
{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
||||||
|
{MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PostedPrices: []pricefeed.PostedPrice{
|
PostedPrices: []pricefeed.PostedPrice{
|
||||||
@ -98,6 +99,12 @@ func NewPricefeedGenStateMulti() app.GenesisState {
|
|||||||
Price: sdk.MustNewDecFromStr("0.25"),
|
Price: sdk.MustNewDecFromStr("0.25"),
|
||||||
Expiry: time.Now().Add(1 * time.Hour),
|
Expiry: time.Now().Add(1 * time.Hour),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MarketID: "bnb:usd",
|
||||||
|
OracleAddress: sdk.AccAddress{},
|
||||||
|
Price: sdk.MustNewDecFromStr("17.25"),
|
||||||
|
Expiry: time.Now().Add(1 * time.Hour),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)}
|
return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)}
|
||||||
@ -105,7 +112,7 @@ func NewPricefeedGenStateMulti() app.GenesisState {
|
|||||||
func NewCDPGenStateMulti() app.GenesisState {
|
func NewCDPGenStateMulti() app.GenesisState {
|
||||||
cdpGenesis := cdp.GenesisState{
|
cdpGenesis := cdp.GenesisState{
|
||||||
Params: cdp.Params{
|
Params: cdp.Params{
|
||||||
GlobalDebtLimit: sdk.NewInt64Coin("usdx", 1000000000000),
|
GlobalDebtLimit: sdk.NewInt64Coin("usdx", 1500000000000),
|
||||||
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
|
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
|
||||||
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
SurplusAuctionLot: cdp.DefaultSurplusLot,
|
||||||
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
|
||||||
@ -136,6 +143,18 @@ func NewCDPGenStateMulti() app.GenesisState {
|
|||||||
LiquidationMarketID: "btc:usd",
|
LiquidationMarketID: "btc:usd",
|
||||||
ConversionFactor: i(8),
|
ConversionFactor: i(8),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Denom: "bnb",
|
||||||
|
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
|
||||||
|
DebtLimit: sdk.NewInt64Coin("usdx", 500000000000),
|
||||||
|
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr
|
||||||
|
LiquidationPenalty: d("0.05"),
|
||||||
|
AuctionSize: i(50000000000),
|
||||||
|
Prefix: 0x22,
|
||||||
|
SpotMarketID: "bnb:usd",
|
||||||
|
LiquidationMarketID: "bnb:usd",
|
||||||
|
ConversionFactor: i(8),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
DebtParam: cdp.DebtParam{
|
DebtParam: cdp.DebtParam{
|
||||||
Denom: "usdx",
|
Denom: "usdx",
|
||||||
|
@ -45,7 +45,10 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace,
|
|||||||
// CdpDenomIndexIterator returns an sdk.Iterator for all cdps with matching collateral denom
|
// CdpDenomIndexIterator returns an sdk.Iterator for all cdps with matching collateral denom
|
||||||
func (k Keeper) CdpDenomIndexIterator(ctx sdk.Context, denom string) sdk.Iterator {
|
func (k Keeper) CdpDenomIndexIterator(ctx sdk.Context, denom string) sdk.Iterator {
|
||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.CdpKeyPrefix)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.CdpKeyPrefix)
|
||||||
db, _ := k.GetDenomPrefix(ctx, denom)
|
db, found := k.GetDenomPrefix(ctx, denom)
|
||||||
|
if !found {
|
||||||
|
panic(fmt.Sprintf("denom %s prefix not found", denom))
|
||||||
|
}
|
||||||
return sdk.KVStorePrefixIterator(store, types.DenomIterKey(db))
|
return sdk.KVStorePrefixIterator(store, types.DenomIterKey(db))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +56,10 @@ func (k Keeper) CdpDenomIndexIterator(ctx sdk.Context, denom string) sdk.Iterato
|
|||||||
// matching denom and collateral:debt ratio LESS THAN targetRatio
|
// matching denom and collateral:debt ratio LESS THAN targetRatio
|
||||||
func (k Keeper) CdpCollateralRatioIndexIterator(ctx sdk.Context, denom string, targetRatio sdk.Dec) sdk.Iterator {
|
func (k Keeper) CdpCollateralRatioIndexIterator(ctx sdk.Context, denom string, targetRatio sdk.Dec) sdk.Iterator {
|
||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.CollateralRatioIndexPrefix)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.CollateralRatioIndexPrefix)
|
||||||
db, _ := k.GetDenomPrefix(ctx, denom)
|
db, found := k.GetDenomPrefix(ctx, denom)
|
||||||
|
if !found {
|
||||||
|
panic(fmt.Sprintf("denom %s prefix not found", denom))
|
||||||
|
}
|
||||||
return store.Iterator(types.CollateralRatioIterKey(db, sdk.ZeroDec()), types.CollateralRatioIterKey(db, targetRatio))
|
return store.Iterator(types.CollateralRatioIterKey(db, sdk.ZeroDec()), types.CollateralRatioIterKey(db, targetRatio))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
81
x/cdp/keeper/math.go
Normal file
81
x/cdp/keeper/math.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
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