mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-10-31 16:37:54 +00:00 
			
		
		
		
	Incentive: savings module reward accumulation logic (#1207)
* implement savings reward accumulator logic * update builder/params with util methods * accumulation test cases
This commit is contained in:
		
							parent
							
								
									72e8f2f40f
								
							
						
					
					
						commit
						db5e839079
					
				| @ -25,4 +25,7 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper) { | ||||
| 	for _, rp := range params.SwapRewardPeriods { | ||||
| 		k.AccumulateSwapRewards(ctx, rp) | ||||
| 	} | ||||
| 	for _, rp := range params.SavingsRewardPeriods { | ||||
| 		k.AccumulateSavingsRewards(ctx, rp) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -64,6 +64,17 @@ func (k Keeper) GetDelegatorRewardPeriods(ctx sdk.Context, denom string) (types. | ||||
| 	return types.MultiRewardPeriod{}, false | ||||
| } | ||||
| 
 | ||||
| // GetSavingsRewardPeriods returns the reward period with the specified collateral type if it's found in the params
 | ||||
| func (k Keeper) GetSavingsRewardPeriods(ctx sdk.Context, denom string) (types.MultiRewardPeriod, bool) { | ||||
| 	params := k.GetParams(ctx) | ||||
| 	for _, rp := range params.SavingsRewardPeriods { | ||||
| 		if rp.CollateralType == denom { | ||||
| 			return rp, true | ||||
| 		} | ||||
| 	} | ||||
| 	return types.MultiRewardPeriod{}, false | ||||
| } | ||||
| 
 | ||||
| // GetMultiplierByDenom fetches a multiplier from the params matching the denom and name.
 | ||||
| func (k Keeper) GetMultiplierByDenom(ctx sdk.Context, denom string, name string) (types.Multiplier, bool) { | ||||
| 	params := k.GetParams(ctx) | ||||
|  | ||||
							
								
								
									
										37
									
								
								x/incentive/keeper/rewards_savings.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								x/incentive/keeper/rewards_savings.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| package keeper | ||||
| 
 | ||||
| import ( | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 
 | ||||
| 	"github.com/kava-labs/kava/x/incentive/types" | ||||
| 	savingstypes "github.com/kava-labs/kava/x/savings/types" | ||||
| ) | ||||
| 
 | ||||
| // AccumulateSavingsRewards calculates new rewards to distribute this block and updates the global indexes
 | ||||
| func (k Keeper) AccumulateSavingsRewards(ctx sdk.Context, rewardPeriod types.MultiRewardPeriod) { | ||||
| 
 | ||||
| 	previousAccrualTime, found := k.GetSavingsRewardAccrualTime(ctx, rewardPeriod.CollateralType) | ||||
| 	if !found { | ||||
| 		previousAccrualTime = ctx.BlockTime() | ||||
| 	} | ||||
| 
 | ||||
| 	indexes, found := k.GetSavingsRewardIndexes(ctx, rewardPeriod.CollateralType) | ||||
| 	if !found { | ||||
| 		indexes = types.RewardIndexes{} | ||||
| 	} | ||||
| 
 | ||||
| 	acc := types.NewAccumulator(previousAccrualTime, indexes) | ||||
| 
 | ||||
| 	savingsMacc := k.accountKeeper.GetModuleAccount(ctx, savingstypes.ModuleName) | ||||
| 	maccCoins := k.bankKeeper.GetAllBalances(ctx, savingsMacc.GetAddress()) | ||||
| 	denomBalance := maccCoins.AmountOf(rewardPeriod.CollateralType) | ||||
| 
 | ||||
| 	acc.Accumulate(rewardPeriod, denomBalance.ToDec(), ctx.BlockTime()) | ||||
| 
 | ||||
| 	k.SetSavingsRewardAccrualTime(ctx, rewardPeriod.CollateralType, acc.PreviousAccumulationTime) | ||||
| 
 | ||||
| 	if len(acc.Indexes) > 0 { | ||||
| 		// the store panics when setting empty or nil indexes
 | ||||
| 		k.SetSavingsRewardIndexes(ctx, rewardPeriod.CollateralType, acc.Indexes) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										162
									
								
								x/incentive/keeper/rewards_savings_accum_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								x/incentive/keeper/rewards_savings_accum_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,162 @@ | ||||
| package keeper_test | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 	"time" | ||||
| 
 | ||||
| 	sdk "github.com/cosmos/cosmos-sdk/types" | ||||
| 	"github.com/stretchr/testify/suite" | ||||
| 	tmproto "github.com/tendermint/tendermint/proto/tendermint/types" | ||||
| 
 | ||||
| 	savingskeeper "github.com/kava-labs/kava/x/savings/keeper" | ||||
| 	savingstypes "github.com/kava-labs/kava/x/savings/types" | ||||
| 
 | ||||
| 	"github.com/kava-labs/kava/app" | ||||
| 	"github.com/kava-labs/kava/x/incentive/keeper" | ||||
| 	"github.com/kava-labs/kava/x/incentive/testutil" | ||||
| 	"github.com/kava-labs/kava/x/incentive/types" | ||||
| ) | ||||
| 
 | ||||
| // Test suite used for all keeper tests
 | ||||
| type SavingsRewardsTestSuite struct { | ||||
| 	suite.Suite | ||||
| 
 | ||||
| 	keeper        keeper.Keeper | ||||
| 	savingsKeeper savingskeeper.Keeper | ||||
| 
 | ||||
| 	app app.TestApp | ||||
| 	ctx sdk.Context | ||||
| 
 | ||||
| 	genesisTime time.Time | ||||
| 	addrs       []sdk.AccAddress | ||||
| } | ||||
| 
 | ||||
| // SetupTest is run automatically before each suite test
 | ||||
| func (suite *SavingsRewardsTestSuite) SetupTest() { | ||||
| 	config := sdk.GetConfig() | ||||
| 	app.SetBech32AddressPrefixes(config) | ||||
| 
 | ||||
| 	_, allAddrs := app.GeneratePrivKeyAddressPairs(10) | ||||
| 	suite.addrs = allAddrs[:5] | ||||
| 	suite.genesisTime = time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC) | ||||
| } | ||||
| 
 | ||||
| func (suite *SavingsRewardsTestSuite) SetupApp() { | ||||
| 	suite.app = app.NewTestApp() | ||||
| 
 | ||||
| 	suite.keeper = suite.app.GetIncentiveKeeper() | ||||
| 	suite.savingsKeeper = suite.app.GetSavingsKeeper() | ||||
| 
 | ||||
| 	suite.ctx = suite.app.NewContext(true, tmproto.Header{Height: 1, Time: suite.genesisTime}) | ||||
| } | ||||
| 
 | ||||
| func (suite *SavingsRewardsTestSuite) SetupWithGenState(authBuilder *app.AuthBankGenesisBuilder, incentBuilder testutil.IncentiveGenesisBuilder, | ||||
| 	savingsGenesis savingstypes.GenesisState) { | ||||
| 	suite.SetupApp() | ||||
| 
 | ||||
| 	suite.app.InitializeFromGenesisStatesWithTime( | ||||
| 		suite.genesisTime, | ||||
| 		authBuilder.BuildMarshalled(suite.app.AppCodec()), | ||||
| 		app.GenesisState{savingstypes.ModuleName: suite.app.AppCodec().MustMarshalJSON(&savingsGenesis)}, | ||||
| 		incentBuilder.BuildMarshalled(suite.app.AppCodec()), | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
| func (suite *SavingsRewardsTestSuite) TestAccumulateSavingsRewards() { | ||||
| 	type args struct { | ||||
| 		deposit               sdk.Coin | ||||
| 		rewardsPerSecond      sdk.Coins | ||||
| 		timeElapsed           int | ||||
| 		expectedRewardIndexes types.RewardIndexes | ||||
| 	} | ||||
| 	type test struct { | ||||
| 		name string | ||||
| 		args args | ||||
| 	} | ||||
| 	testCases := []test{ | ||||
| 		{ | ||||
| 			"7 seconds", | ||||
| 			args{ | ||||
| 				deposit:          c("ukava", 1_000_000), | ||||
| 				rewardsPerSecond: cs(c("hard", 122354)), | ||||
| 				timeElapsed:      7, | ||||
| 				expectedRewardIndexes: types.RewardIndexes{ | ||||
| 					types.NewRewardIndex("hard", d("0.856478000000000000")), | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"1 day", | ||||
| 			args{ | ||||
| 				deposit:          c("ukava", 1_000_000), | ||||
| 				rewardsPerSecond: cs(c("hard", 122354)), | ||||
| 				timeElapsed:      86400, | ||||
| 				expectedRewardIndexes: types.RewardIndexes{ | ||||
| 					types.NewRewardIndex("hard", d("10571.385600000000000000")), | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"0 seconds", | ||||
| 			args{ | ||||
| 				deposit:          c("ukava", 1_000_000), | ||||
| 				rewardsPerSecond: cs(c("hard", 122354)), | ||||
| 				timeElapsed:      0, | ||||
| 				expectedRewardIndexes: types.RewardIndexes{ | ||||
| 					types.NewRewardIndex("hard", d("0.0")), | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"multiple reward coins", | ||||
| 			args{ | ||||
| 				deposit:          c("ukava", 1_000_000), | ||||
| 				rewardsPerSecond: cs(c("hard", 122354), c("bnb", 567889)), | ||||
| 				timeElapsed:      7, | ||||
| 				expectedRewardIndexes: types.RewardIndexes{ | ||||
| 					types.NewRewardIndex("bnb", d("3.97522300000000000")), | ||||
| 					types.NewRewardIndex("hard", d("0.856478000000000000")), | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tc := range testCases { | ||||
| 		suite.Run(tc.name, func() { | ||||
| 			params := savingstypes.NewParams( | ||||
| 				[]string{"ukava"}, | ||||
| 			) | ||||
| 			deposits := savingstypes.Deposits{ | ||||
| 				savingstypes.NewDeposit( | ||||
| 					suite.addrs[0], | ||||
| 					sdk.NewCoins(tc.args.deposit), | ||||
| 				), | ||||
| 			} | ||||
| 			savingsGenesis := savingstypes.NewGenesisState(params, deposits) | ||||
| 
 | ||||
| 			authBuilder := app.NewAuthBankGenesisBuilder(). | ||||
| 				WithSimpleAccount(suite.addrs[0], cs(c("ukava", 1e9))). | ||||
| 				WithSimpleModuleAccount(savingstypes.ModuleName, sdk.NewCoins(tc.args.deposit)) | ||||
| 
 | ||||
| 			incentBuilder := testutil.NewIncentiveGenesisBuilder(). | ||||
| 				WithGenesisTime(suite.genesisTime). | ||||
| 				WithSimpleSavingsRewardPeriod(tc.args.deposit.Denom, tc.args.rewardsPerSecond) | ||||
| 
 | ||||
| 			suite.SetupWithGenState(authBuilder, incentBuilder, savingsGenesis) | ||||
| 
 | ||||
| 			// Set up chain context at future time
 | ||||
| 			runAtTime := suite.ctx.BlockTime().Add(time.Duration(int(time.Second) * tc.args.timeElapsed)) | ||||
| 			runCtx := suite.ctx.WithBlockTime(runAtTime) | ||||
| 
 | ||||
| 			rewardPeriods, found := suite.keeper.GetSavingsRewardPeriods(runCtx, tc.args.deposit.Denom) | ||||
| 			suite.Require().True(found) | ||||
| 			suite.keeper.AccumulateSavingsRewards(runCtx, rewardPeriods) | ||||
| 
 | ||||
| 			rewardIndexes, _ := suite.keeper.GetSavingsRewardIndexes(runCtx, tc.args.deposit.Denom) | ||||
| 			suite.Require().Equal(tc.args.expectedRewardIndexes, rewardIndexes) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestSavingsRewardsTestSuite(t *testing.T) { | ||||
| 	suite.Run(t, new(SavingsRewardsTestSuite)) | ||||
| } | ||||
| @ -252,3 +252,26 @@ func NewStandardMoneyMarket(denom string) hardtypes.MoneyMarket { | ||||
| 		sdk.ZeroDec(), | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
| // WithInitializedSavingsRewardPeriod sets the genesis time as the previous accumulation time for the specified period.
 | ||||
| // This can be helpful in tests. With no prev time set, the first block accrues no rewards as it just sets the prev time to the current.
 | ||||
| func (builder IncentiveGenesisBuilder) WithInitializedSavingsRewardPeriod(period types.MultiRewardPeriod) IncentiveGenesisBuilder { | ||||
| 	builder.Params.SavingsRewardPeriods = append(builder.Params.SavingsRewardPeriods, period) | ||||
| 
 | ||||
| 	accumulationTimeForPeriod := types.NewAccumulationTime(period.CollateralType, builder.genesisTime) | ||||
| 	builder.SavingsRewardState.AccumulationTimes = append( | ||||
| 		builder.SavingsRewardState.AccumulationTimes, | ||||
| 		accumulationTimeForPeriod, | ||||
| 	) | ||||
| 
 | ||||
| 	builder.SavingsRewardState.MultiRewardIndexes = builder.SavingsRewardState.MultiRewardIndexes.With( | ||||
| 		period.CollateralType, | ||||
| 		newZeroRewardIndexesFromCoins(period.RewardsPerSecond...), | ||||
| 	) | ||||
| 
 | ||||
| 	return builder | ||||
| } | ||||
| 
 | ||||
| func (builder IncentiveGenesisBuilder) WithSimpleSavingsRewardPeriod(ctype string, rewardsPerSecond sdk.Coins) IncentiveGenesisBuilder { | ||||
| 	return builder.WithInitializedSavingsRewardPeriod(builder.simpleRewardPeriod(ctype, rewardsPerSecond)) | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Denali Marsh
						Denali Marsh