mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 02:07:52 +00:00 
			
		
		
		
	Fix delegation claim syncing (#889)
* sync claims on validator state changes and slashes * add test notes * update missed sync delegator calls * tidy up suite addresses initialization * test claim synced when validator bonds/unbonds * test validator slashed * check reward factor increased * test redelegation sync claim * revert mistake * resolve trailing TODOs * call incentive hooks after hard liquidation * check global index in tests after delegator reward sync Co-authored-by: denalimarsh <denalimarsh@gmail.com> Co-authored-by: karzak <kjydavis3@gmail.com>
This commit is contained in:
		
							parent
							
								
									bf15ec4f8d
								
							
						
					
					
						commit
						54c0793ced
					
				@ -61,8 +61,13 @@ func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress,
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	deposit.Amount = sdk.NewCoins()
 | 
			
		||||
	k.DeleteDeposit(ctx, deposit)
 | 
			
		||||
	k.AfterDepositModified(ctx, deposit)
 | 
			
		||||
 | 
			
		||||
	borrow.Amount = sdk.NewCoins()
 | 
			
		||||
	k.DeleteBorrow(ctx, borrow)
 | 
			
		||||
	k.AfterBorrowModified(ctx, borrow)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -66,31 +66,70 @@ func (h Hooks) AfterBorrowModified(ctx sdk.Context, borrow hardtypes.Borrow) {
 | 
			
		||||
	h.k.UpdateHardBorrowIndexDenoms(ctx, borrow)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ------------------- Staking Module Hooks -------------------
 | 
			
		||||
/* ------------------- Staking Module Hooks -------------------
 | 
			
		||||
 | 
			
		||||
Rewards are calculated based on total delegated tokens to bonded validators (not shares).
 | 
			
		||||
We need to sync the claim before the user's delegated tokens are changed.
 | 
			
		||||
 | 
			
		||||
When delegated tokens (to bonded validators) are changed:
 | 
			
		||||
- user creates new delegation
 | 
			
		||||
  - total bonded delegation increases
 | 
			
		||||
- user delegates or beginUnbonding or beginRedelegate an existing delegation
 | 
			
		||||
  - total bonded delegation increases or decreases
 | 
			
		||||
- validator is slashed and Jailed/Tombstoned (tokens reduce, and validator is unbonded)
 | 
			
		||||
  - slash: total bonded delegation decreases (less tokens)
 | 
			
		||||
  - jail: total bonded delegation decreases (tokens no longer bonded (after end blocker runs))
 | 
			
		||||
- validator becomes unbonded (ie when they drop out of the top 100)
 | 
			
		||||
  - total bonded delegation decreases (tokens no longer bonded)
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
// BeforeDelegationCreated runs before a delegation is created
 | 
			
		||||
func (h Hooks) BeforeDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
 | 
			
		||||
	// Add a claim if one doesn't exist, otherwise sync the existing.
 | 
			
		||||
	h.k.InitializeHardDelegatorReward(ctx, delAddr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BeforeDelegationSharesModified runs before an existing delegation is modified
 | 
			
		||||
func (h Hooks) BeforeDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
 | 
			
		||||
	h.k.SynchronizeHardDelegatorRewards(ctx, delAddr)
 | 
			
		||||
	// Sync rewards based on total delegated to bonded validators.
 | 
			
		||||
	h.k.SynchronizeHardDelegatorRewards(ctx, delAddr, nil, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NOTE: following hooks are just implemented to ensure StakingHooks interface compliance
 | 
			
		||||
 | 
			
		||||
// BeforeValidatorSlashed is called before a validator is slashed
 | 
			
		||||
func (h Hooks) BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) {}
 | 
			
		||||
// Validator status is not updated when Slash or Jail is called
 | 
			
		||||
func (h Hooks) BeforeValidatorSlashed(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) {
 | 
			
		||||
	// Sync all claims for users delegated to this validator.
 | 
			
		||||
	// For each claim, sync based on the total delegated to bonded validators.
 | 
			
		||||
	for _, delegation := range h.k.stakingKeeper.GetValidatorDelegations(ctx, valAddr) {
 | 
			
		||||
		h.k.SynchronizeHardDelegatorRewards(ctx, delegation.DelegatorAddress, nil, false)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AfterValidatorBeginUnbonding is called after a validator begins unbonding
 | 
			
		||||
// Validator status is set to Unbonding prior to hook running
 | 
			
		||||
func (h Hooks) AfterValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) {
 | 
			
		||||
	// Sync all claims for users delegated to this validator.
 | 
			
		||||
	// For each claim, sync based on the total delegated to bonded validators, and also delegations to valAddr.
 | 
			
		||||
	// valAddr's status has just been set to Unbonding, but we want to include delegations to it in the sync.
 | 
			
		||||
	for _, delegation := range h.k.stakingKeeper.GetValidatorDelegations(ctx, valAddr) {
 | 
			
		||||
		h.k.SynchronizeHardDelegatorRewards(ctx, delegation.DelegatorAddress, valAddr, true)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AfterValidatorBonded is called after a validator is bonded
 | 
			
		||||
// Validator status is set to Bonded prior to hook running
 | 
			
		||||
func (h Hooks) AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) {
 | 
			
		||||
	// Sync all claims for users delegated to this validator.
 | 
			
		||||
	// For each claim, sync based on the total delegated to bonded validators, except for delegations to valAddr.
 | 
			
		||||
	// valAddr's status has just been set to Bonded, but we don't want to include delegations to it in the sync
 | 
			
		||||
	for _, delegation := range h.k.stakingKeeper.GetValidatorDelegations(ctx, valAddr) {
 | 
			
		||||
		h.k.SynchronizeHardDelegatorRewards(ctx, delegation.DelegatorAddress, valAddr, false)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NOTE: following hooks are just implemented to ensure StakingHooks interface compliance
 | 
			
		||||
 | 
			
		||||
// AfterDelegationModified runs after a delegation is modified
 | 
			
		||||
func (h Hooks) AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -209,20 +209,11 @@ func NewStakingGenesisState() app.GenesisState {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *KeeperTestSuite) SetupWithGenState() {
 | 
			
		||||
	config := sdk.GetConfig()
 | 
			
		||||
	app.SetBech32AddressPrefixes(config)
 | 
			
		||||
 | 
			
		||||
	_, allAddrs := app.GeneratePrivKeyAddressPairs(10)
 | 
			
		||||
	suite.addrs = allAddrs[:5]
 | 
			
		||||
	for _, a := range allAddrs[5:] {
 | 
			
		||||
		suite.validatorAddrs = append(suite.validatorAddrs, sdk.ValAddress(a))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tApp := app.NewTestApp()
 | 
			
		||||
	ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
 | 
			
		||||
 | 
			
		||||
	tApp.InitializeFromGenesisStates(
 | 
			
		||||
		NewAuthGenState(allAddrs, cs(c("ukava", 1_000_000_000))),
 | 
			
		||||
		NewAuthGenState(suite.getAllAddrs(), cs(c("ukava", 1_000_000_000))),
 | 
			
		||||
		NewStakingGenesisState(),
 | 
			
		||||
		NewPricefeedGenStateMulti(),
 | 
			
		||||
		NewCDPGenStateMulti(),
 | 
			
		||||
 | 
			
		||||
@ -38,17 +38,33 @@ type KeeperTestSuite struct {
 | 
			
		||||
 | 
			
		||||
// The default state used by each test
 | 
			
		||||
func (suite *KeeperTestSuite) SetupTest() {
 | 
			
		||||
	config := sdk.GetConfig()
 | 
			
		||||
	app.SetBech32AddressPrefixes(config)
 | 
			
		||||
 | 
			
		||||
	_, allAddrs := app.GeneratePrivKeyAddressPairs(10)
 | 
			
		||||
	suite.addrs = allAddrs[:5]
 | 
			
		||||
	for _, a := range allAddrs[5:] {
 | 
			
		||||
		suite.validatorAddrs = append(suite.validatorAddrs, sdk.ValAddress(a))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tApp := app.NewTestApp()
 | 
			
		||||
	ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
 | 
			
		||||
 | 
			
		||||
	tApp.InitializeFromGenesisStates()
 | 
			
		||||
 | 
			
		||||
	_, addrs := app.GeneratePrivKeyAddressPairs(5)
 | 
			
		||||
	keeper := tApp.GetIncentiveKeeper()
 | 
			
		||||
	suite.keeper = tApp.GetIncentiveKeeper()
 | 
			
		||||
	suite.app = tApp
 | 
			
		||||
	suite.ctx = ctx
 | 
			
		||||
	suite.keeper = keeper
 | 
			
		||||
	suite.addrs = addrs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getAllAddrs returns all user and validator addresses in the suite
 | 
			
		||||
func (suite *KeeperTestSuite) getAllAddrs() []sdk.AccAddress {
 | 
			
		||||
	accAddrs := []sdk.AccAddress{} // initialize new slice to avoid accidental modifications to underlying
 | 
			
		||||
	accAddrs = append(accAddrs, suite.addrs...)
 | 
			
		||||
	for _, a := range suite.validatorAddrs {
 | 
			
		||||
		accAddrs = append(accAddrs, sdk.AccAddress(a))
 | 
			
		||||
	}
 | 
			
		||||
	return accAddrs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *KeeperTestSuite) getAccount(addr sdk.AccAddress) authexported.Account {
 | 
			
		||||
 | 
			
		||||
@ -271,10 +271,7 @@ func (k Keeper) InitializeHardSupplyReward(ctx sdk.Context, deposit hardtypes.De
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	claim, found := k.GetHardLiquidityProviderClaim(ctx, deposit.Depositor)
 | 
			
		||||
	if found {
 | 
			
		||||
		// Reset borrow reward indexes
 | 
			
		||||
		claim.BorrowRewardIndexes = types.MultiRewardIndexes{}
 | 
			
		||||
	} else {
 | 
			
		||||
	if !found {
 | 
			
		||||
		// Instantiate claim object
 | 
			
		||||
		claim = types.NewHardLiquidityProviderClaim(deposit.Depositor, sdk.Coins{}, nil, nil, nil)
 | 
			
		||||
	}
 | 
			
		||||
@ -496,8 +493,10 @@ func (k Keeper) UpdateHardBorrowIndexDenoms(ctx sdk.Context, borrow hardtypes.Bo
 | 
			
		||||
	k.SetHardLiquidityProviderClaim(ctx, claim)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SynchronizeHardDelegatorRewards updates the claim object by adding any accumulated rewards
 | 
			
		||||
func (k Keeper) SynchronizeHardDelegatorRewards(ctx sdk.Context, delegator sdk.AccAddress) {
 | 
			
		||||
// SynchronizeHardDelegatorRewards updates the claim object by adding any accumulated rewards, and setting the reward indexes to the global values.
 | 
			
		||||
// valAddr and shouldIncludeValidator are used to ignore or include delegations to a particular validator when summing up the total delegation.
 | 
			
		||||
// Normally only delegations to Bonded validators are included in the total. This is needed as staking hooks are sometimes called on the wrong side of a validator's state update (from this module's perspective).
 | 
			
		||||
func (k Keeper) SynchronizeHardDelegatorRewards(ctx sdk.Context, delegator sdk.AccAddress, valAddr sdk.ValAddress, shouldIncludeValidator bool) {
 | 
			
		||||
	claim, found := k.GetHardLiquidityProviderClaim(ctx, delegator)
 | 
			
		||||
	if !found {
 | 
			
		||||
		return
 | 
			
		||||
@ -529,9 +528,16 @@ func (k Keeper) SynchronizeHardDelegatorRewards(ctx sdk.Context, delegator sdk.A
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Delegators don't accumulate rewards if their validator is unbonded/slashed
 | 
			
		||||
		if validator.GetStatus() != sdk.Bonded {
 | 
			
		||||
			continue
 | 
			
		||||
		if valAddr == nil {
 | 
			
		||||
			// Delegators don't accumulate rewards if their validator is unbonded
 | 
			
		||||
			if validator.GetStatus() != sdk.Bonded {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			if !shouldIncludeValidator && validator.OperatorAddress.Equals(valAddr) {
 | 
			
		||||
				// ignore tokens delegated to the validator
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if validator.GetTokens().IsZero() {
 | 
			
		||||
@ -601,7 +607,7 @@ func (k Keeper) InitializeHardDelegatorReward(ctx sdk.Context, delegator sdk.Acc
 | 
			
		||||
		// Instantiate claim object
 | 
			
		||||
		claim = types.NewHardLiquidityProviderClaim(delegator, sdk.Coins{}, nil, nil, nil)
 | 
			
		||||
	} else {
 | 
			
		||||
		k.SynchronizeHardDelegatorRewards(ctx, delegator)
 | 
			
		||||
		k.SynchronizeHardDelegatorRewards(ctx, delegator, nil, false)
 | 
			
		||||
		claim, _ = k.GetHardLiquidityProviderClaim(ctx, delegator)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -652,7 +658,7 @@ func (k Keeper) SynchronizeHardLiquidityProviderClaim(ctx sdk.Context, owner sdk
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Synchronize any hard delegator rewards
 | 
			
		||||
	k.SynchronizeHardDelegatorRewards(ctx, owner)
 | 
			
		||||
	k.SynchronizeHardDelegatorRewards(ctx, owner, nil, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ZeroHardLiquidityProviderClaim zeroes out the claim object's rewards and returns the updated claim object
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,10 @@ import (
 | 
			
		||||
	"github.com/kava-labs/kava/x/incentive/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	oneYear time.Duration = time.Hour * 24 * 365
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (suite *KeeperTestSuite) TestAccumulateUSDXMintingRewards() {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		ctype                 string
 | 
			
		||||
@ -885,7 +889,7 @@ func (suite *KeeperTestSuite) TestAccumulateHardDelegatorRewards() {
 | 
			
		||||
 | 
			
		||||
			err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], tc.args.delegation)
 | 
			
		||||
			suite.Require().NoError(err)
 | 
			
		||||
			suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[0], tc.args.delegation)
 | 
			
		||||
			err = suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[0], tc.args.delegation)
 | 
			
		||||
			suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
			staking.EndBlocker(suite.ctx, suite.stakingKeeper)
 | 
			
		||||
@ -2407,7 +2411,7 @@ func (suite *KeeperTestSuite) TestSynchronizeHardDelegatorReward() {
 | 
			
		||||
 | 
			
		||||
			// After we've accumulated, run synchronize
 | 
			
		||||
			suite.Require().NotPanics(func() {
 | 
			
		||||
				suite.keeper.SynchronizeHardDelegatorRewards(suite.ctx, suite.addrs[0])
 | 
			
		||||
				suite.keeper.SynchronizeHardDelegatorRewards(suite.ctx, suite.addrs[0], nil, false)
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			// Check that reward factor and claim have been updated as expected
 | 
			
		||||
@ -2767,7 +2771,7 @@ func (suite *KeeperTestSuite) TestSimulateHardDelegatorRewardSynchronization() {
 | 
			
		||||
			// Delegator delegates
 | 
			
		||||
			err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], tc.args.delegation)
 | 
			
		||||
			suite.Require().NoError(err)
 | 
			
		||||
			suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[0], tc.args.delegation)
 | 
			
		||||
			err = suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[0], tc.args.delegation)
 | 
			
		||||
			suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
			staking.EndBlocker(suite.ctx, suite.stakingKeeper)
 | 
			
		||||
@ -2944,3 +2948,348 @@ func (suite *KeeperTestSuite) deliverMsgDelegate(ctx sdk.Context, delegator sdk.
 | 
			
		||||
	_, err := handleStakingMsg(ctx, msg)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *KeeperTestSuite) deliverMsgRedelegate(ctx sdk.Context, delegator sdk.AccAddress, sourceValidator, destinationValidator sdk.ValAddress, amount sdk.Coin) error {
 | 
			
		||||
	msg := staking.NewMsgBeginRedelegate(
 | 
			
		||||
		delegator,
 | 
			
		||||
		sourceValidator,
 | 
			
		||||
		destinationValidator,
 | 
			
		||||
		amount,
 | 
			
		||||
	)
 | 
			
		||||
	handleStakingMsg := staking.NewHandler(suite.stakingKeeper)
 | 
			
		||||
	_, err := handleStakingMsg(ctx, msg)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// given a user has a delegation to a bonded validator, when the validator starts unbonding, the user does not accumulate rewards
 | 
			
		||||
func (suite *KeeperTestSuite) TestUnbondingValidatorSyncsClaim() {
 | 
			
		||||
	suite.SetupWithGenState()
 | 
			
		||||
	initialTime := time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC)
 | 
			
		||||
	suite.ctx = suite.ctx.WithBlockTime(initialTime)
 | 
			
		||||
	blockDuration := 10 * time.Second
 | 
			
		||||
 | 
			
		||||
	// Setup incentive state
 | 
			
		||||
	rewardsPerSecond := c("hard", 122354)
 | 
			
		||||
	bondDenom := "ukava"
 | 
			
		||||
	params := types.NewParams(
 | 
			
		||||
		nil,
 | 
			
		||||
		nil,
 | 
			
		||||
		nil,
 | 
			
		||||
		types.RewardPeriods{
 | 
			
		||||
			types.NewRewardPeriod(true, bondDenom, initialTime.Add(-1*oneYear), initialTime.Add(4*oneYear), rewardsPerSecond),
 | 
			
		||||
		},
 | 
			
		||||
		types.DefaultMultipliers,
 | 
			
		||||
		initialTime.Add(5*oneYear),
 | 
			
		||||
	)
 | 
			
		||||
	suite.keeper.SetParams(suite.ctx, params)
 | 
			
		||||
	suite.keeper.SetPreviousHardDelegatorRewardAccrualTime(suite.ctx, bondDenom, initialTime)
 | 
			
		||||
	suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, bondDenom, sdk.ZeroDec())
 | 
			
		||||
 | 
			
		||||
	// Reduce the size of the validator set
 | 
			
		||||
	stakingParams := suite.app.GetStakingKeeper().GetParams(suite.ctx)
 | 
			
		||||
	stakingParams.MaxValidators = 2
 | 
			
		||||
	suite.app.GetStakingKeeper().SetParams(suite.ctx, stakingParams)
 | 
			
		||||
 | 
			
		||||
	// Create 3 validators
 | 
			
		||||
	err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], c(bondDenom, 10_000_000))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
	err = suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[1], c(bondDenom, 5_000_000))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
	err = suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[2], c(bondDenom, 1_000_000))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	// End the block so top validators become bonded
 | 
			
		||||
	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{})
 | 
			
		||||
 | 
			
		||||
	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(1 * blockDuration))
 | 
			
		||||
	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}) // height and time in header are ignored by module begin blockers
 | 
			
		||||
 | 
			
		||||
	// Delegate to a bonded validator from the test user. This will initialize their incentive claim.
 | 
			
		||||
	err = suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[1], c(bondDenom, 1_000_000))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	// Start a new block to accumulate some delegation rewards for the user.
 | 
			
		||||
	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{})
 | 
			
		||||
	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(2 * blockDuration))
 | 
			
		||||
	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}) // height and time in header are ignored by module begin blockers
 | 
			
		||||
 | 
			
		||||
	// Delegate to the unbonded validator to push it into the bonded validator set, pushing out the user's delegated validator
 | 
			
		||||
	err = suite.deliverMsgDelegate(suite.ctx, suite.addrs[2], suite.validatorAddrs[2], c(bondDenom, 8_000_000))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	// End the block to start unbonding the user's validator
 | 
			
		||||
	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{})
 | 
			
		||||
	// but don't start the next block as it will accumulate delegator rewards and we won't be able to tell if the user's reward was synced.
 | 
			
		||||
 | 
			
		||||
	// Check that the user's claim has been synced. ie rewards added, index updated
 | 
			
		||||
	claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0])
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
 | 
			
		||||
	globalIndex, found := suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, bondDenom)
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
	claimIndex, found := claim.DelegatorRewardIndexes.GetRewardIndex(bondDenom)
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
	suite.Require().Equal(globalIndex, claimIndex.RewardFactor)
 | 
			
		||||
 | 
			
		||||
	suite.Require().Equal(
 | 
			
		||||
		cs(c(rewardsPerSecond.Denom, 76471)),
 | 
			
		||||
		claim.Reward,
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	// Run another block and check the claim is not accumulating more rewards
 | 
			
		||||
	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(3 * blockDuration))
 | 
			
		||||
	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{})
 | 
			
		||||
 | 
			
		||||
	suite.keeper.SynchronizeHardDelegatorRewards(suite.ctx, suite.addrs[0], nil, false)
 | 
			
		||||
 | 
			
		||||
	// rewards are the same as before
 | 
			
		||||
	laterClaim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0])
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
	suite.Require().Equal(claim.Reward, laterClaim.Reward)
 | 
			
		||||
 | 
			
		||||
	// claim index has been updated to latest global value
 | 
			
		||||
	laterClaimIndex, found := laterClaim.DelegatorRewardIndexes.GetRewardIndex(bondDenom)
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
	globalIndex, found = suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, bondDenom)
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
	suite.Require().Equal(globalIndex, laterClaimIndex.RewardFactor)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// given a user has a delegation to an unbonded validator, when the validator becomes bonded, the user starts accumulating rewards
 | 
			
		||||
func (suite *KeeperTestSuite) TestBondingValidatorSyncsClaim() {
 | 
			
		||||
	suite.SetupWithGenState()
 | 
			
		||||
	initialTime := time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC)
 | 
			
		||||
	suite.ctx = suite.ctx.WithBlockTime(initialTime)
 | 
			
		||||
	blockDuration := 10 * time.Second
 | 
			
		||||
 | 
			
		||||
	// Setup incentive state
 | 
			
		||||
	rewardsPerSecond := c("hard", 122354)
 | 
			
		||||
	bondDenom := "ukava"
 | 
			
		||||
	params := types.NewParams(
 | 
			
		||||
		nil,
 | 
			
		||||
		nil,
 | 
			
		||||
		nil,
 | 
			
		||||
		types.RewardPeriods{
 | 
			
		||||
			types.NewRewardPeriod(true, bondDenom, initialTime.Add(-1*oneYear), initialTime.Add(4*oneYear), rewardsPerSecond),
 | 
			
		||||
		},
 | 
			
		||||
		types.DefaultMultipliers,
 | 
			
		||||
		initialTime.Add(5*oneYear),
 | 
			
		||||
	)
 | 
			
		||||
	suite.keeper.SetParams(suite.ctx, params)
 | 
			
		||||
	suite.keeper.SetPreviousHardDelegatorRewardAccrualTime(suite.ctx, bondDenom, initialTime)
 | 
			
		||||
	suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, bondDenom, sdk.ZeroDec())
 | 
			
		||||
 | 
			
		||||
	// Reduce the size of the validator set
 | 
			
		||||
	stakingParams := suite.app.GetStakingKeeper().GetParams(suite.ctx)
 | 
			
		||||
	stakingParams.MaxValidators = 2
 | 
			
		||||
	suite.app.GetStakingKeeper().SetParams(suite.ctx, stakingParams)
 | 
			
		||||
 | 
			
		||||
	// Create 3 validators
 | 
			
		||||
	err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], c(bondDenom, 10_000_000))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
	err = suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[1], c(bondDenom, 5_000_000))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
	err = suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[2], c(bondDenom, 1_000_000))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	// End the block so top validators become bonded
 | 
			
		||||
	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{})
 | 
			
		||||
 | 
			
		||||
	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(1 * blockDuration))
 | 
			
		||||
	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}) // height and time in header are ignored by module begin blockers
 | 
			
		||||
 | 
			
		||||
	// Delegate to an unbonded validator from the test user. This will initialize their incentive claim.
 | 
			
		||||
	err = suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[2], c(bondDenom, 1_000_000))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	// Start a new block to accumulate some delegation rewards globally.
 | 
			
		||||
	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{})
 | 
			
		||||
	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(2 * blockDuration))
 | 
			
		||||
	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{})
 | 
			
		||||
 | 
			
		||||
	// Delegate to the user's unbonded validator to push it into the bonded validator set
 | 
			
		||||
	err = suite.deliverMsgDelegate(suite.ctx, suite.addrs[2], suite.validatorAddrs[2], c(bondDenom, 4_000_000))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	// End the block to bond the user's validator
 | 
			
		||||
	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{})
 | 
			
		||||
	// but don't start the next block as it will accumulate delegator rewards and we won't be able to tell if the user's reward was synced.
 | 
			
		||||
 | 
			
		||||
	// Check that the user's claim has been synced. ie rewards added, index updated
 | 
			
		||||
	claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0])
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
 | 
			
		||||
	globalIndex, found := suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, bondDenom)
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
	claimIndex, found := claim.DelegatorRewardIndexes.GetRewardIndex(bondDenom)
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
	suite.Require().Equal(globalIndex, claimIndex.RewardFactor)
 | 
			
		||||
 | 
			
		||||
	suite.Require().Equal(
 | 
			
		||||
		sdk.Coins(nil),
 | 
			
		||||
		claim.Reward,
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	// Run another block and check the claim is accumulating more rewards
 | 
			
		||||
	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(3 * blockDuration))
 | 
			
		||||
	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{})
 | 
			
		||||
 | 
			
		||||
	suite.keeper.SynchronizeHardDelegatorRewards(suite.ctx, suite.addrs[0], nil, false)
 | 
			
		||||
 | 
			
		||||
	// rewards are greater than before
 | 
			
		||||
	laterClaim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0])
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
	suite.Require().True(laterClaim.Reward.IsAllGT(claim.Reward))
 | 
			
		||||
 | 
			
		||||
	// claim index has been updated to latest global value
 | 
			
		||||
	laterClaimIndex, found := laterClaim.DelegatorRewardIndexes.GetRewardIndex(bondDenom)
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
	globalIndex, found = suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, bondDenom)
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
	suite.Require().Equal(globalIndex, laterClaimIndex.RewardFactor)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// If a validator is slashed delegators should have their claims synced
 | 
			
		||||
func (suite *KeeperTestSuite) TestSlashingValidatorSyncsClaim() {
 | 
			
		||||
	suite.SetupWithGenState()
 | 
			
		||||
	initialTime := time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC)
 | 
			
		||||
	suite.ctx = suite.ctx.WithBlockTime(initialTime)
 | 
			
		||||
	blockDuration := 10 * time.Second
 | 
			
		||||
 | 
			
		||||
	// Setup incentive state
 | 
			
		||||
	rewardsPerSecond := c("hard", 122354)
 | 
			
		||||
	bondDenom := "ukava"
 | 
			
		||||
	params := types.NewParams(
 | 
			
		||||
		nil,
 | 
			
		||||
		nil,
 | 
			
		||||
		nil,
 | 
			
		||||
		types.RewardPeriods{
 | 
			
		||||
			types.NewRewardPeriod(true, bondDenom, initialTime.Add(-1*oneYear), initialTime.Add(4*oneYear), rewardsPerSecond),
 | 
			
		||||
		},
 | 
			
		||||
		types.DefaultMultipliers,
 | 
			
		||||
		initialTime.Add(5*oneYear),
 | 
			
		||||
	)
 | 
			
		||||
	suite.keeper.SetParams(suite.ctx, params)
 | 
			
		||||
	suite.keeper.SetPreviousHardDelegatorRewardAccrualTime(suite.ctx, bondDenom, initialTime.Add(-1*blockDuration))
 | 
			
		||||
	suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, bondDenom, sdk.ZeroDec())
 | 
			
		||||
 | 
			
		||||
	// Reduce the size of the validator set
 | 
			
		||||
	stakingParams := suite.app.GetStakingKeeper().GetParams(suite.ctx)
 | 
			
		||||
	stakingParams.MaxValidators = 2
 | 
			
		||||
	suite.app.GetStakingKeeper().SetParams(suite.ctx, stakingParams)
 | 
			
		||||
 | 
			
		||||
	// Create 2 validators
 | 
			
		||||
	err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], c(bondDenom, 10_000_000))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
	err = suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[1], c(bondDenom, 10_000_000))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	// End the block so validators become bonded
 | 
			
		||||
	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{})
 | 
			
		||||
 | 
			
		||||
	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(1 * blockDuration))
 | 
			
		||||
	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}) // height and time in header are ignored by module begin blockers
 | 
			
		||||
 | 
			
		||||
	// Delegate to a bonded validator from the test user. This will initialize their incentive claim.
 | 
			
		||||
	err = suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[1], c(bondDenom, 1_000_000))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	// Check that claim has been created with synced reward index but no reward coins
 | 
			
		||||
	initialClaim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0])
 | 
			
		||||
	suite.True(found)
 | 
			
		||||
	initialGlobalIndex, found := suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, bondDenom)
 | 
			
		||||
	suite.True(found)
 | 
			
		||||
	initialClaimIndex, found := initialClaim.DelegatorRewardIndexes.GetRewardIndex(bondDenom)
 | 
			
		||||
	suite.True(found)
 | 
			
		||||
	suite.Require().Equal(initialGlobalIndex, initialClaimIndex.RewardFactor)
 | 
			
		||||
	suite.True(initialClaim.Reward.Empty()) // Initial claim should not have any rewards
 | 
			
		||||
 | 
			
		||||
	// Start a new block to accumulate some delegation rewards for the user.
 | 
			
		||||
	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{})
 | 
			
		||||
	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(2 * blockDuration))
 | 
			
		||||
	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}) // height and time in header are ignored by module begin blockers
 | 
			
		||||
 | 
			
		||||
	// Fetch validator and slash them
 | 
			
		||||
	stakingKeeper := suite.app.GetStakingKeeper()
 | 
			
		||||
	validator, found := stakingKeeper.GetValidator(suite.ctx, suite.validatorAddrs[1])
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
	suite.Require().True(validator.GetTokens().IsPositive())
 | 
			
		||||
	fraction := sdk.NewDecWithPrec(5, 1)
 | 
			
		||||
	stakingKeeper.Slash(suite.ctx, validator.ConsAddress(), suite.ctx.BlockHeight(), 10, fraction)
 | 
			
		||||
 | 
			
		||||
	// Check that the user's claim has been synced. ie rewards added, index updated
 | 
			
		||||
	claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0])
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
	globalIndex, found := suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, bondDenom)
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
	claimIndex, found := claim.DelegatorRewardIndexes.GetRewardIndex(bondDenom)
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
	suite.Require().Equal(globalIndex, claimIndex.RewardFactor)
 | 
			
		||||
 | 
			
		||||
	// Check that rewards were added
 | 
			
		||||
	suite.Require().Equal(
 | 
			
		||||
		cs(c(rewardsPerSecond.Denom, 58264)),
 | 
			
		||||
		claim.Reward,
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	// Check that reward factor increased from initial value
 | 
			
		||||
	suite.True(claimIndex.RewardFactor.GT(initialClaimIndex.RewardFactor))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Given a delegation to a bonded validator, when a user redelegates everything to another (bonded) validator, the user's claim is synced
 | 
			
		||||
func (suite *KeeperTestSuite) TestRedelegationSyncsClaim() {
 | 
			
		||||
	suite.SetupWithGenState()
 | 
			
		||||
	initialTime := time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC)
 | 
			
		||||
	suite.ctx = suite.ctx.WithBlockTime(initialTime)
 | 
			
		||||
	blockDuration := 10 * time.Second
 | 
			
		||||
 | 
			
		||||
	// Setup incentive state
 | 
			
		||||
	rewardsPerSecond := c("hard", 122354)
 | 
			
		||||
	bondDenom := "ukava"
 | 
			
		||||
	params := types.NewParams(
 | 
			
		||||
		nil,
 | 
			
		||||
		nil,
 | 
			
		||||
		nil,
 | 
			
		||||
		types.RewardPeriods{
 | 
			
		||||
			types.NewRewardPeriod(true, bondDenom, initialTime.Add(-1*oneYear), initialTime.Add(4*oneYear), rewardsPerSecond),
 | 
			
		||||
		},
 | 
			
		||||
		types.DefaultMultipliers,
 | 
			
		||||
		initialTime.Add(5*oneYear),
 | 
			
		||||
	)
 | 
			
		||||
	suite.keeper.SetParams(suite.ctx, params)
 | 
			
		||||
	suite.keeper.SetPreviousHardDelegatorRewardAccrualTime(suite.ctx, bondDenom, initialTime)
 | 
			
		||||
	suite.keeper.SetHardDelegatorRewardFactor(suite.ctx, bondDenom, sdk.ZeroDec())
 | 
			
		||||
 | 
			
		||||
	// Create 2 validators
 | 
			
		||||
	err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], c(bondDenom, 10_000_000))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
	err = suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[1], c(bondDenom, 5_000_000))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	// Delegatefrom the test user. This will initialize their incentive claim.
 | 
			
		||||
	err = suite.deliverMsgDelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[0], c(bondDenom, 1_000_000))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	// Start a new block to accumulate some delegation rewards globally.
 | 
			
		||||
	_ = suite.app.EndBlocker(suite.ctx, abci.RequestEndBlock{})
 | 
			
		||||
	suite.ctx = suite.ctx.WithBlockTime(initialTime.Add(1 * blockDuration))
 | 
			
		||||
	_ = suite.app.BeginBlocker(suite.ctx, abci.RequestBeginBlock{}) // height and time in header are ignored by module begin blockers
 | 
			
		||||
 | 
			
		||||
	// Redelegate the user's delegation between the two validators. This should trigger hooks that sync the user's claim.
 | 
			
		||||
	err = suite.deliverMsgRedelegate(suite.ctx, suite.addrs[0], suite.validatorAddrs[0], suite.validatorAddrs[1], c(bondDenom, 1_000_000))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	// Check that the user's claim has been synced. ie rewards added, index updated
 | 
			
		||||
	claim, found := suite.keeper.GetHardLiquidityProviderClaim(suite.ctx, suite.addrs[0])
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
 | 
			
		||||
	globalIndex, found := suite.keeper.GetHardDelegatorRewardFactor(suite.ctx, bondDenom)
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
	claimIndex, found := claim.DelegatorRewardIndexes.GetRewardIndex(bondDenom)
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
	suite.Require().Equal(globalIndex, claimIndex.RewardFactor)
 | 
			
		||||
	suite.Require().Equal(
 | 
			
		||||
		cs(c(rewardsPerSecond.Denom, 76471)),
 | 
			
		||||
		claim.Reward,
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ type SupplyKeeper interface {
 | 
			
		||||
// StakingKeeper defines the expected staking keeper for module accounts
 | 
			
		||||
type StakingKeeper interface {
 | 
			
		||||
	GetDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAddress, maxRetrieve uint16) (delegations []stakingtypes.Delegation)
 | 
			
		||||
	GetValidatorDelegations(ctx sdk.Context, valAddr sdk.ValAddress) (delegations []stakingtypes.Delegation)
 | 
			
		||||
	GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator stakingtypes.Validator, found bool)
 | 
			
		||||
	TotalBondedTokens(ctx sdk.Context) sdk.Int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user