mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 08:15:19 +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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deposit.Amount = sdk.NewCoins()
|
||||||
k.DeleteDeposit(ctx, deposit)
|
k.DeleteDeposit(ctx, deposit)
|
||||||
|
k.AfterDepositModified(ctx, deposit)
|
||||||
|
|
||||||
|
borrow.Amount = sdk.NewCoins()
|
||||||
k.DeleteBorrow(ctx, borrow)
|
k.DeleteBorrow(ctx, borrow)
|
||||||
|
k.AfterBorrowModified(ctx, borrow)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,31 +66,70 @@ func (h Hooks) AfterBorrowModified(ctx sdk.Context, borrow hardtypes.Borrow) {
|
|||||||
h.k.UpdateHardBorrowIndexDenoms(ctx, 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
|
// BeforeDelegationCreated runs before a delegation is created
|
||||||
func (h Hooks) BeforeDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
|
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)
|
h.k.InitializeHardDelegatorReward(ctx, delAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeforeDelegationSharesModified runs before an existing delegation is modified
|
// BeforeDelegationSharesModified runs before an existing delegation is modified
|
||||||
func (h Hooks) BeforeDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
|
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
|
// 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
|
// 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) {
|
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
|
// 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) {
|
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
|
// AfterDelegationModified runs after a delegation is modified
|
||||||
func (h Hooks) AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
|
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() {
|
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()
|
tApp := app.NewTestApp()
|
||||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
||||||
|
|
||||||
tApp.InitializeFromGenesisStates(
|
tApp.InitializeFromGenesisStates(
|
||||||
NewAuthGenState(allAddrs, cs(c("ukava", 1_000_000_000))),
|
NewAuthGenState(suite.getAllAddrs(), cs(c("ukava", 1_000_000_000))),
|
||||||
NewStakingGenesisState(),
|
NewStakingGenesisState(),
|
||||||
NewPricefeedGenStateMulti(),
|
NewPricefeedGenStateMulti(),
|
||||||
NewCDPGenStateMulti(),
|
NewCDPGenStateMulti(),
|
||||||
|
@ -38,17 +38,33 @@ type KeeperTestSuite struct {
|
|||||||
|
|
||||||
// The default state used by each test
|
// The default state used by each test
|
||||||
func (suite *KeeperTestSuite) SetupTest() {
|
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()
|
tApp := app.NewTestApp()
|
||||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
||||||
|
|
||||||
tApp.InitializeFromGenesisStates()
|
tApp.InitializeFromGenesisStates()
|
||||||
|
|
||||||
_, addrs := app.GeneratePrivKeyAddressPairs(5)
|
suite.keeper = tApp.GetIncentiveKeeper()
|
||||||
keeper := tApp.GetIncentiveKeeper()
|
|
||||||
suite.app = tApp
|
suite.app = tApp
|
||||||
suite.ctx = ctx
|
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 {
|
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)
|
claim, found := k.GetHardLiquidityProviderClaim(ctx, deposit.Depositor)
|
||||||
if found {
|
if !found {
|
||||||
// Reset borrow reward indexes
|
|
||||||
claim.BorrowRewardIndexes = types.MultiRewardIndexes{}
|
|
||||||
} else {
|
|
||||||
// Instantiate claim object
|
// Instantiate claim object
|
||||||
claim = types.NewHardLiquidityProviderClaim(deposit.Depositor, sdk.Coins{}, nil, nil, nil)
|
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)
|
k.SetHardLiquidityProviderClaim(ctx, claim)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SynchronizeHardDelegatorRewards updates the claim object by adding any accumulated rewards
|
// SynchronizeHardDelegatorRewards updates the claim object by adding any accumulated rewards, and setting the reward indexes to the global values.
|
||||||
func (k Keeper) SynchronizeHardDelegatorRewards(ctx sdk.Context, delegator sdk.AccAddress) {
|
// 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)
|
claim, found := k.GetHardLiquidityProviderClaim(ctx, delegator)
|
||||||
if !found {
|
if !found {
|
||||||
return
|
return
|
||||||
@ -529,10 +528,17 @@ func (k Keeper) SynchronizeHardDelegatorRewards(ctx sdk.Context, delegator sdk.A
|
|||||||
continue
|
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 {
|
if validator.GetStatus() != sdk.Bonded {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if !shouldIncludeValidator && validator.OperatorAddress.Equals(valAddr) {
|
||||||
|
// ignore tokens delegated to the validator
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if validator.GetTokens().IsZero() {
|
if validator.GetTokens().IsZero() {
|
||||||
continue
|
continue
|
||||||
@ -601,7 +607,7 @@ func (k Keeper) InitializeHardDelegatorReward(ctx sdk.Context, delegator sdk.Acc
|
|||||||
// Instantiate claim object
|
// Instantiate claim object
|
||||||
claim = types.NewHardLiquidityProviderClaim(delegator, sdk.Coins{}, nil, nil, nil)
|
claim = types.NewHardLiquidityProviderClaim(delegator, sdk.Coins{}, nil, nil, nil)
|
||||||
} else {
|
} else {
|
||||||
k.SynchronizeHardDelegatorRewards(ctx, delegator)
|
k.SynchronizeHardDelegatorRewards(ctx, delegator, nil, false)
|
||||||
claim, _ = k.GetHardLiquidityProviderClaim(ctx, delegator)
|
claim, _ = k.GetHardLiquidityProviderClaim(ctx, delegator)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -652,7 +658,7 @@ func (k Keeper) SynchronizeHardLiquidityProviderClaim(ctx sdk.Context, owner sdk
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Synchronize any hard delegator rewards
|
// 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
|
// 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"
|
"github.com/kava-labs/kava/x/incentive/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
oneYear time.Duration = time.Hour * 24 * 365
|
||||||
|
)
|
||||||
|
|
||||||
func (suite *KeeperTestSuite) TestAccumulateUSDXMintingRewards() {
|
func (suite *KeeperTestSuite) TestAccumulateUSDXMintingRewards() {
|
||||||
type args struct {
|
type args struct {
|
||||||
ctype string
|
ctype string
|
||||||
@ -885,7 +889,7 @@ func (suite *KeeperTestSuite) TestAccumulateHardDelegatorRewards() {
|
|||||||
|
|
||||||
err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], tc.args.delegation)
|
err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], tc.args.delegation)
|
||||||
suite.Require().NoError(err)
|
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)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
staking.EndBlocker(suite.ctx, suite.stakingKeeper)
|
staking.EndBlocker(suite.ctx, suite.stakingKeeper)
|
||||||
@ -2407,7 +2411,7 @@ func (suite *KeeperTestSuite) TestSynchronizeHardDelegatorReward() {
|
|||||||
|
|
||||||
// After we've accumulated, run synchronize
|
// After we've accumulated, run synchronize
|
||||||
suite.Require().NotPanics(func() {
|
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
|
// Check that reward factor and claim have been updated as expected
|
||||||
@ -2767,7 +2771,7 @@ func (suite *KeeperTestSuite) TestSimulateHardDelegatorRewardSynchronization() {
|
|||||||
// Delegator delegates
|
// Delegator delegates
|
||||||
err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], tc.args.delegation)
|
err := suite.deliverMsgCreateValidator(suite.ctx, suite.validatorAddrs[0], tc.args.delegation)
|
||||||
suite.Require().NoError(err)
|
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)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
staking.EndBlocker(suite.ctx, suite.stakingKeeper)
|
staking.EndBlocker(suite.ctx, suite.stakingKeeper)
|
||||||
@ -2944,3 +2948,348 @@ func (suite *KeeperTestSuite) deliverMsgDelegate(ctx sdk.Context, delegator sdk.
|
|||||||
_, err := handleStakingMsg(ctx, msg)
|
_, err := handleStakingMsg(ctx, msg)
|
||||||
return err
|
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
|
// StakingKeeper defines the expected staking keeper for module accounts
|
||||||
type StakingKeeper interface {
|
type StakingKeeper interface {
|
||||||
GetDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAddress, maxRetrieve uint16) (delegations []stakingtypes.Delegation)
|
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)
|
GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator stakingtypes.Validator, found bool)
|
||||||
TotalBondedTokens(ctx sdk.Context) sdk.Int
|
TotalBondedTokens(ctx sdk.Context) sdk.Int
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user