mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-04-04 15:55:23 +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
	 Nick DeLuca
						Nick DeLuca