mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 00:05:18 +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,10 +528,17 @@ func (k Keeper) SynchronizeHardDelegatorRewards(ctx sdk.Context, delegator sdk.A
|
||||
continue
|
||||
}
|
||||
|
||||
// Delegators don't accumulate rewards if their validator is unbonded/slashed
|
||||
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() {
|
||||
continue
|
||||
@ -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