mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 00:05:18 +00:00
Optimize CDP Begin Blocker (#1822)
* optimize cdp begin blocker by removing unnecessary checks, reusing data and prefix stores in loops, and reducing number of repeated calculations * fix panic for new cdp types if both previous accural time and global interest factor are not set * do not touch global interest factor if no CDP's exist; revert to panic if global interest factor is not found since this is an unreachable state by normal keeper operation -- it can only be reached if store is modified outside of public interface and normal operation
This commit is contained in:
parent
673790465d
commit
6ea518960a
@ -47,7 +47,7 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper)
|
|||||||
|
|
||||||
ctx.Logger().Debug(fmt.Sprintf("running x/cdp SynchronizeInterestForRiskyCDPs and LiquidateCdps for %s", cp.Type))
|
ctx.Logger().Debug(fmt.Sprintf("running x/cdp SynchronizeInterestForRiskyCDPs and LiquidateCdps for %s", cp.Type))
|
||||||
|
|
||||||
err = k.SynchronizeInterestForRiskyCDPs(ctx, cp.CheckCollateralizationIndexCount, sdk.MaxSortableDec, cp.Type)
|
err = k.SynchronizeInterestForRiskyCDPs(ctx, sdk.MaxSortableDec, cp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
sdkmath "cosmossdk.io/math"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/types/simulation"
|
"github.com/cosmos/cosmos-sdk/types/simulation"
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ import (
|
|||||||
"github.com/kava-labs/kava/x/cdp"
|
"github.com/kava-labs/kava/x/cdp"
|
||||||
"github.com/kava-labs/kava/x/cdp/keeper"
|
"github.com/kava-labs/kava/x/cdp/keeper"
|
||||||
"github.com/kava-labs/kava/x/cdp/types"
|
"github.com/kava-labs/kava/x/cdp/types"
|
||||||
|
pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ModuleTestSuite struct {
|
type ModuleTestSuite struct {
|
||||||
@ -43,7 +45,7 @@ func (suite *ModuleTestSuite) SetupTest() {
|
|||||||
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()})
|
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()})
|
||||||
tracker := liquidationTracker{}
|
tracker := liquidationTracker{}
|
||||||
|
|
||||||
coins := cs(c("btc", 100000000), c("xrp", 10000000000))
|
coins := cs(c("btc", 100000000), c("xrp", 10000000000), c("erc20/usdc", 10000000000))
|
||||||
_, addrs := app.GeneratePrivKeyAddressPairs(100)
|
_, addrs := app.GeneratePrivKeyAddressPairs(100)
|
||||||
authGS := app.NewFundedGenStateWithSameCoins(tApp.AppCodec(), coins, addrs)
|
authGS := app.NewFundedGenStateWithSameCoins(tApp.AppCodec(), coins, addrs)
|
||||||
tApp.InitializeFromGenesisStates(
|
tApp.InitializeFromGenesisStates(
|
||||||
@ -65,7 +67,7 @@ func (suite *ModuleTestSuite) createCdps() {
|
|||||||
cdps := make(types.CDPs, 100)
|
cdps := make(types.CDPs, 100)
|
||||||
tracker := liquidationTracker{}
|
tracker := liquidationTracker{}
|
||||||
|
|
||||||
coins := cs(c("btc", 100000000), c("xrp", 10000000000))
|
coins := cs(c("btc", 100000000), c("xrp", 10000000000), c("erc20/usdc", 10000000000))
|
||||||
_, addrs := app.GeneratePrivKeyAddressPairs(100)
|
_, addrs := app.GeneratePrivKeyAddressPairs(100)
|
||||||
|
|
||||||
authGS := app.NewFundedGenStateWithSameCoins(tApp.AppCodec(), coins, addrs)
|
authGS := app.NewFundedGenStateWithSameCoins(tApp.AppCodec(), coins, addrs)
|
||||||
@ -124,6 +126,86 @@ func (suite *ModuleTestSuite) setPrice(price sdk.Dec, market string) {
|
|||||||
suite.Equal(price, pp.Price)
|
suite.Equal(price, pp.Price)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *ModuleTestSuite) TestBeginBlockNewCdpTypeSetsGlobalInterest() {
|
||||||
|
suite.createCdps()
|
||||||
|
|
||||||
|
// add a new collateral that does not have previous accumulation time or global interest factor set
|
||||||
|
params := suite.keeper.GetParams(suite.ctx)
|
||||||
|
usdcCollateral := types.CollateralParam{
|
||||||
|
Denom: "erc20/usdc",
|
||||||
|
Type: "erc20-usdc",
|
||||||
|
LiquidationRatio: sdk.MustNewDecFromStr("1.01"),
|
||||||
|
DebtLimit: sdk.NewInt64Coin("usdx", 500000000000),
|
||||||
|
StabilityFee: sdk.OneDec(),
|
||||||
|
AuctionSize: sdkmath.NewIntFromUint64(10000000000),
|
||||||
|
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
|
||||||
|
CheckCollateralizationIndexCount: sdkmath.NewInt(10),
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
|
SpotMarketID: "usdc:usd",
|
||||||
|
LiquidationMarketID: "usdc:usd",
|
||||||
|
ConversionFactor: sdkmath.NewInt(6),
|
||||||
|
}
|
||||||
|
usdtCollateral := types.CollateralParam{
|
||||||
|
Denom: "erc20/usdt",
|
||||||
|
Type: "erc20-usdt",
|
||||||
|
LiquidationRatio: sdk.MustNewDecFromStr("1.01"),
|
||||||
|
DebtLimit: sdk.NewInt64Coin("usdx", 500000000000),
|
||||||
|
StabilityFee: sdk.OneDec(),
|
||||||
|
AuctionSize: sdkmath.NewIntFromUint64(10000000000),
|
||||||
|
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
|
||||||
|
CheckCollateralizationIndexCount: sdkmath.NewInt(10),
|
||||||
|
KeeperRewardPercentage: sdk.MustNewDecFromStr("0.01"),
|
||||||
|
SpotMarketID: "usdt:usd",
|
||||||
|
LiquidationMarketID: "usdt:usd",
|
||||||
|
ConversionFactor: sdkmath.NewInt(18),
|
||||||
|
}
|
||||||
|
newCollaterals := []types.CollateralParam{usdcCollateral, usdtCollateral}
|
||||||
|
params.CollateralParams = append(params.CollateralParams, newCollaterals...)
|
||||||
|
suite.keeper.SetParams(suite.ctx, params)
|
||||||
|
|
||||||
|
// setup market for cdp collateral
|
||||||
|
priceFeedKeeper := suite.app.GetPriceFeedKeeper()
|
||||||
|
priceParams := priceFeedKeeper.GetParams(suite.ctx)
|
||||||
|
newMarkets := []pricefeedtypes.Market{
|
||||||
|
{MarketID: "usdc:usd", BaseAsset: "usdc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
||||||
|
{MarketID: "usdt:usd", BaseAsset: "usdt", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
|
||||||
|
}
|
||||||
|
priceParams.Markets = append(priceParams.Markets, newMarkets...)
|
||||||
|
priceFeedKeeper.SetParams(suite.ctx, priceParams)
|
||||||
|
suite.setPrice(d("1"), "usdc:usd")
|
||||||
|
suite.keeper.UpdatePricefeedStatus(suite.ctx, usdcCollateral.SpotMarketID)
|
||||||
|
suite.setPrice(d("1"), "usdt:usd")
|
||||||
|
suite.keeper.UpdatePricefeedStatus(suite.ctx, usdtCollateral.SpotMarketID)
|
||||||
|
|
||||||
|
// create a CDP for USDC, no CDPS for USDT
|
||||||
|
err := suite.keeper.AddCdp(suite.ctx, suite.addrs[0], c(usdcCollateral.Denom, 100000000), c("usdx", 10000000), usdcCollateral.Type)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// ensure begin block does not panic due to no accumulation time or no global interest factor
|
||||||
|
suite.Require().NotPanics(func() {
|
||||||
|
cdp.BeginBlocker(suite.ctx, abci.RequestBeginBlock{Header: suite.ctx.BlockHeader()}, suite.keeper)
|
||||||
|
}, "expected begin blocker not to panic")
|
||||||
|
|
||||||
|
// set by accumulate interest (or add cdp above)
|
||||||
|
// usdc has accural time set
|
||||||
|
previousAccrualTime, found := suite.keeper.GetPreviousAccrualTime(suite.ctx, usdcCollateral.Type)
|
||||||
|
suite.Require().True(found, "expected previous accrual time for new market to be set")
|
||||||
|
suite.Equal(suite.ctx.BlockTime(), previousAccrualTime, "expected previous accrual time to equal block time")
|
||||||
|
// usdt has accural time set
|
||||||
|
previousAccrualTime, found = suite.keeper.GetPreviousAccrualTime(suite.ctx, usdtCollateral.Type)
|
||||||
|
suite.Require().True(found, "expected previous accrual time for new market to be set")
|
||||||
|
suite.Equal(suite.ctx.BlockTime(), previousAccrualTime, "expected previous accrual time to equal block time")
|
||||||
|
|
||||||
|
// set for USDC by AddCdp
|
||||||
|
globalInterestFactor, found := suite.keeper.GetInterestFactor(suite.ctx, usdcCollateral.Type)
|
||||||
|
suite.Require().True(found, "expected global interest factor for new collateral to be set")
|
||||||
|
suite.Equal(sdk.OneDec(), globalInterestFactor, "expected global interest factor to equal 1")
|
||||||
|
// not set for USDT since it has no cdps
|
||||||
|
globalInterestFactor, found = suite.keeper.GetInterestFactor(suite.ctx, usdtCollateral.Type)
|
||||||
|
suite.Require().False(found, "expected global interest factor for new collateral to not be set")
|
||||||
|
suite.Equal(sdk.ZeroDec(), globalInterestFactor, "expected global interest factor to equal 0")
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *ModuleTestSuite) TestBeginBlock() {
|
func (suite *ModuleTestSuite) TestBeginBlock() {
|
||||||
// test setup, creating
|
// test setup, creating
|
||||||
// 50 xrp cdps each with
|
// 50 xrp cdps each with
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
|
|
||||||
sdkmath "cosmossdk.io/math"
|
sdkmath "cosmossdk.io/math"
|
||||||
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/x/cdp/types"
|
"github.com/kava-labs/kava/x/cdp/types"
|
||||||
@ -161,11 +162,110 @@ func (k Keeper) CalculateNewInterest(ctx sdk.Context, cdp types.CDP) sdk.Coin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SynchronizeInterestForRiskyCDPs synchronizes the interest for the slice of cdps with the lowest collateral:debt ratio
|
// SynchronizeInterestForRiskyCDPs synchronizes the interest for the slice of cdps with the lowest collateral:debt ratio
|
||||||
func (k Keeper) SynchronizeInterestForRiskyCDPs(ctx sdk.Context, slice sdkmath.Int, targetRatio sdk.Dec, collateralType string) error {
|
func (k Keeper) SynchronizeInterestForRiskyCDPs(ctx sdk.Context, targetRatio sdk.Dec, cp types.CollateralParam) error {
|
||||||
cdps := k.GetSliceOfCDPsByRatioAndType(ctx, slice, targetRatio, collateralType)
|
debtParam := k.GetParams(ctx).DebtParam
|
||||||
for _, cdp := range cdps {
|
|
||||||
k.hooks.BeforeCDPModified(ctx, cdp)
|
cdpStore := prefix.NewStore(ctx.KVStore(k.key), types.CdpKeyPrefix)
|
||||||
k.SynchronizeInterest(ctx, cdp)
|
collateralRatioStore := prefix.NewStore(ctx.KVStore(k.key), types.CollateralRatioIndexPrefix)
|
||||||
|
|
||||||
|
cdpIDs := make([]uint64, 0, cp.CheckCollateralizationIndexCount.Int64())
|
||||||
|
|
||||||
|
iterator := collateralRatioStore.Iterator(types.CollateralRatioIterKey(cp.Type, sdk.ZeroDec()), types.CollateralRatioIterKey(cp.Type, targetRatio))
|
||||||
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
|
_, id, _ := types.SplitCollateralRatioKey(iterator.Key())
|
||||||
|
cdpIDs = append(cdpIDs, id)
|
||||||
|
if int64(len(cdpIDs)) >= cp.CheckCollateralizationIndexCount.Int64() {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
iterator.Close()
|
||||||
|
|
||||||
|
globalInterestFactor, found := k.GetInterestFactor(ctx, cp.Type)
|
||||||
|
if !found && len(cdpIDs) > 0 {
|
||||||
|
panic(fmt.Sprintf("global interest factor not found for type %s", cp.Type))
|
||||||
|
}
|
||||||
|
prevAccrualTime, found := k.GetPreviousAccrualTime(ctx, cp.Type)
|
||||||
|
if !found {
|
||||||
|
panic(fmt.Sprintf("previous accrual time not found for type %s", cp.Type))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cdpID := range cdpIDs {
|
||||||
|
//
|
||||||
|
// GET CDP
|
||||||
|
//
|
||||||
|
bz := cdpStore.Get(types.CdpKey(cp.Type, cdpID))
|
||||||
|
if bz == nil {
|
||||||
|
panic(fmt.Sprintf("cdp %d does not exist", cdpID))
|
||||||
|
}
|
||||||
|
var cdp types.CDP
|
||||||
|
k.cdc.MustUnmarshal(bz, &cdp)
|
||||||
|
|
||||||
|
if debtParam.Denom != cdp.GetTotalPrincipal().Denom {
|
||||||
|
panic(fmt.Sprintf("unkown debt param %s", cdp.GetTotalPrincipal().Denom))
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// HOOK
|
||||||
|
//
|
||||||
|
k.hooks.BeforeCDPModified(ctx, cdp)
|
||||||
|
|
||||||
|
//
|
||||||
|
// CALC INTEREST
|
||||||
|
//
|
||||||
|
accumulatedInterest := sdk.ZeroInt()
|
||||||
|
cdpInterestFactor := globalInterestFactor.Quo(cdp.InterestFactor)
|
||||||
|
if !cdpInterestFactor.Equal(sdk.OneDec()) {
|
||||||
|
accumulatedInterest = sdk.NewDecFromInt(cdp.GetTotalPrincipal().Amount).Mul(cdpInterestFactor).RoundInt().Sub(cdp.GetTotalPrincipal().Amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if accumulatedInterest.IsZero() {
|
||||||
|
// accumulated interest is zero if apy is zero or are if the total fees for all cdps round to zero
|
||||||
|
if cdp.FeesUpdated.Equal(prevAccrualTime) {
|
||||||
|
// if all fees are rounding to zero, don't update FeesUpdated
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// if apy is zero, we need to update FeesUpdated
|
||||||
|
cdp.FeesUpdated = prevAccrualTime
|
||||||
|
bz = k.cdc.MustMarshal(&cdp)
|
||||||
|
cdpStore.Set(types.CdpKey(cdp.Type, cdp.ID), bz)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// GET OLD RATIO
|
||||||
|
//
|
||||||
|
previousCollateralRatio := calculateCollateralRatio(debtParam, cp, cdp)
|
||||||
|
|
||||||
|
//
|
||||||
|
// UPDATE CDP
|
||||||
|
//
|
||||||
|
cdp.AccumulatedFees = cdp.AccumulatedFees.Add(sdk.NewCoin(cdp.AccumulatedFees.Denom, accumulatedInterest))
|
||||||
|
cdp.FeesUpdated = prevAccrualTime
|
||||||
|
cdp.InterestFactor = globalInterestFactor
|
||||||
|
|
||||||
|
//
|
||||||
|
// CALC NEW RATIO
|
||||||
|
//
|
||||||
|
updatedCollateralRatio := calculateCollateralRatio(debtParam, cp, cdp)
|
||||||
|
|
||||||
|
//
|
||||||
|
// UPDATE STORE
|
||||||
|
//
|
||||||
|
collateralRatioStore.Delete(types.CollateralRatioKey(cdp.Type, cdp.ID, previousCollateralRatio))
|
||||||
|
bz = k.cdc.MustMarshal(&cdp)
|
||||||
|
cdpStore.Set(types.CdpKey(cdp.Type, cdp.ID), bz)
|
||||||
|
collateralRatioStore.Set(types.CollateralRatioKey(cdp.Type, cdp.ID, updatedCollateralRatio), types.GetCdpIDBytes(cdp.ID))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func calculateCollateralRatio(debtParam types.DebtParam, collateralParam types.CollateralParam, cdp types.CDP) sdk.Dec {
|
||||||
|
debtTotal := sdk.NewDecFromInt(cdp.GetTotalPrincipal().Amount).Mul(sdk.NewDecFromIntWithPrec(sdk.OneInt(), debtParam.ConversionFactor.Int64()))
|
||||||
|
|
||||||
|
if debtTotal.IsZero() || debtTotal.GTE(types.MaxSortableDec) {
|
||||||
|
return types.MaxSortableDec.Sub(sdk.SmallestDec())
|
||||||
|
} else {
|
||||||
|
collateralBaseUnits := sdk.NewDecFromInt(cdp.Collateral.Amount).Mul(sdk.NewDecFromIntWithPrec(sdk.OneInt(), collateralParam.ConversionFactor.Int64()))
|
||||||
|
return collateralBaseUnits.Quo(debtTotal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -713,7 +713,19 @@ func (suite *InterestTestSuite) TestSyncInterestForRiskyCDPs() {
|
|||||||
err = suite.keeper.AccumulateInterest(suite.ctx, tc.args.ctype)
|
err = suite.keeper.AccumulateInterest(suite.ctx, tc.args.ctype)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
err = suite.keeper.SynchronizeInterestForRiskyCDPs(suite.ctx, i(int64(tc.args.slice)), sdk.MaxSortableDec, tc.args.ctype)
|
params := suite.keeper.GetParams(suite.ctx)
|
||||||
|
var ctype types.CollateralParam
|
||||||
|
|
||||||
|
for _, cp := range params.CollateralParams {
|
||||||
|
if cp.Type == tc.args.ctype {
|
||||||
|
ctype = cp
|
||||||
|
|
||||||
|
cp.CheckCollateralizationIndexCount = sdk.NewInt(int64(tc.args.slice))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = suite.keeper.SynchronizeInterestForRiskyCDPs(suite.ctx, sdk.MaxSortableDec, ctype)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
cdpsUpdatedCount := 0
|
cdpsUpdatedCount := 0
|
||||||
|
Loading…
Reference in New Issue
Block a user