Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fix superfluid delegation edge case on jailed validator #8778

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### State Machine Breaking

* [#8732](https://github.com/osmosis-labs/osmosis/pull/8732) fix: iterate delegations continue instead of erroring

* [#8778](https://github.com/osmosis-labs/osmosis/pull/8778) fix: Fix superfluid delegation edge case on jailed validator
## v26.0.1
Comment on lines +65 to 66
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

State Breaking Changes: Superfluid Delegation Edge Case Fix

This change addresses an edge case in superfluid delegation for jailed validators. It's important to note that this is a state-breaking change, which means it could potentially affect existing data or behavior in the system.

Ensure that this fix is thoroughly tested and that its impact on existing superfluid delegations is well understood before deployment.


### State Machine Breaking
Expand Down
6 changes: 6 additions & 0 deletions x/superfluid/keeper/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,9 @@ func (k Keeper) ConvertUnlockedToStake(ctx sdk.Context, sender sdk.AccAddress, v
func (k Keeper) DelegateBaseOnValsetPref(ctx sdk.Context, sender sdk.AccAddress, valAddr, originalSuperfluidValAddr string, totalAmtToStake osmomath.Int) error {
return k.delegateBaseOnValsetPref(ctx, sender, valAddr, originalSuperfluidValAddr, totalAmtToStake)
}

func (k Keeper) ForceUndelegateAndBurnOsmoTokens(ctx sdk.Context,
osmoAmount osmomath.Int, intermediaryAcc types.SuperfluidIntermediaryAccount,
) error {
return k.forceUndelegateAndBurnOsmoTokens(ctx, osmoAmount, intermediaryAcc)
}
26 changes: 19 additions & 7 deletions x/superfluid/keeper/stake.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

addresscodec "cosmossdk.io/core/address"
errorsmod "cosmossdk.io/errors"
math "cosmossdk.io/math"

"github.com/osmosis-labs/osmosis/osmomath"
"github.com/osmosis-labs/osmosis/osmoutils"
Expand Down Expand Up @@ -503,17 +504,28 @@ func (k Keeper) forceUndelegateAndBurnOsmoTokens(ctx sdk.Context,
if err != nil {
return err
}
// TODO: Better understand and decide between ValidateUnbondAmount and SharesFromTokens
// briefly looked into it, did not understand what's correct.
// TODO: ensure that intermediate account has at least osmoAmount staked.
czarcas7ic marked this conversation as resolved.
Show resolved Hide resolved

shares, err := k.sk.ValidateUnbondAmount(
ctx, intermediaryAcc.GetAccAddress(), valAddr, osmoAmount,
)
if err == stakingtypes.ErrNoDelegation {
return nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still want this check right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, added it back in the latest commit. Thanks for the catch 🙏

} else if err != nil {
return err

if err != nil {
// if ValidateUnbondAmount has failed it indicates that the amount we're trying to unbond is
// greater then what validator has this can be due to different factors (e.g jail)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// greater then what validator has this can be due to different factors (e.g jail)
// greater then what validator has delegated to it. This can be due to different factors (e.g jail)

This actually doesn't quite check out to me as an explanation. If a validator is downtime jailed on Osmosis, there is no slash. So why would the unbond amount tracked be greater than expected post jail?

// in this case we brun min(amount_tpo_burn, total_delegation_share)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// in this case we brun min(amount_tpo_burn, total_delegation_share)
// in this case we run min(amount_to_burn, total_delegation_share)

I think that is what was meant?

validator, err := k.sk.GetValidator(ctx, valAddr)
if err != nil {
return err
}
del, err := k.sk.GetDelegation(ctx, intermediaryAcc.GetAccAddress(), valAddr)
if err != nil {
return err
}

valShares, err := validator.SharesFromTokens(osmoAmount)
czarcas7ic marked this conversation as resolved.
Show resolved Hide resolved
shares = math.LegacyMinDec(del.Shares, valShares)
}

err = osmoutils.ApplyFuncIfNoError(ctx, func(cacheCtx sdk.Context) error {
undelegatedCoins, err := k.sk.InstantUndelegate(cacheCtx, intermediaryAcc.GetAccAddress(), valAddr, shares)
if err != nil {
Expand Down
91 changes: 91 additions & 0 deletions x/superfluid/keeper/stake_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper_test

import (
"fmt"
"time"

"github.com/osmosis-labs/osmosis/osmomath"
Expand All @@ -14,6 +15,8 @@ import (
"github.com/osmosis-labs/osmosis/v26/x/superfluid/types"

errorsmod "cosmossdk.io/errors"
"cosmossdk.io/math"
evidencetypes "cosmossdk.io/x/evidence/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank/testutil"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
Expand Down Expand Up @@ -1049,6 +1052,94 @@ func (s *KeeperTestSuite) TestRefreshIntermediaryDelegationAmounts() {
}
}

func (s *KeeperTestSuite) TestForceUndelegateAndBurnOsmoTokens() {
testCases := []struct {
name string
validatorStats []stakingtypes.BondStatus
superDelegations []superfluidDelegation
jailed bool
jailValWithSmallAmt bool

expectedShareDiff math.LegacyDec
}{
{
"with single validator and single superfluid delegation and single undelegation",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, 0, 0, 1000000}},
false,
false,
math.LegacyMustNewDecFromStr("10"),
},
{
"jailed validator where superfluid delegation was major delegation",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, 0, 0, 1}},
true,
true,
math.LegacyDec{},
},
{
"jailed validator where superfluid delegation was major delegation",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, 0, 0, 1000000}},
true,
false,
math.LegacyMustNewDecFromStr("10.526315789473684210"),
},
}

for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
s.SetupTest()

// setup validators
valAddrs := s.SetupValidators(tc.validatorStats)

denoms, _ := s.SetupGammPoolsAndSuperfluidAssets([]osmomath.Dec{osmomath.NewDec(20), osmomath.NewDec(20)})

// setup superfluid delegations
_, intermediaryAccs, _ := s.setupSuperfluidDelegations(valAddrs, tc.superDelegations, denoms)

delegationBeforeUndelegate, err := s.App.StakingKeeper.GetDelegation(s.Ctx, intermediaryAccs[0].GetAccAddress(), valAddrs[0])
s.Require().NoError(err)

// jail the validator as part of set up
if tc.jailed {
validator, err := s.App.StakingKeeper.GetValidator(s.Ctx, valAddrs[0])
s.Require().NoError(err)
s.Ctx = s.Ctx.WithBlockHeight(100)
consAddr, err := validator.GetConsAddr()
s.Require().NoError(err)
// slash by slash factor
power := sdk.TokensToConsensusPower(validator.Tokens, sdk.DefaultPowerReduction)

// Note: this calls BeforeValidatorSlashed hook
s.handleEquivocationEvidence(s.Ctx, &evidencetypes.Equivocation{
Height: 80,
Time: time.Time{},
Power: power,
ConsensusAddress: sdk.ConsAddress(consAddr).String(),
})
val, err := s.App.StakingKeeper.GetValidatorByConsAddr(s.Ctx, consAddr)
s.Require().NoError(err)
s.Require().Equal(val.Jailed, true)
}

err = s.App.SuperfluidKeeper.ForceUndelegateAndBurnOsmoTokens(s.Ctx, math.NewInt(10), intermediaryAccs[0])
s.Require().NoError(err)

if !tc.jailValWithSmallAmt {
delegationAfterUndelegate, err := s.App.StakingKeeper.GetDelegation(s.Ctx, intermediaryAccs[0].GetAccAddress(), valAddrs[0])
s.Require().NoError(err)

shareDiff := delegationBeforeUndelegate.Shares.Sub(delegationAfterUndelegate.Shares)
fmt.Println("share diff: ", shareDiff.String())
s.Require().True(shareDiff.Equal(tc.expectedShareDiff))
Comment on lines +1137 to +1138
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use s.T().Log instead of fmt.Println for test logging

Using s.T().Log integrates better with the testing framework and provides more control over test output. Replace fmt.Println with s.T().Log to adhere to best practices.

Apply this diff to update the logging:

-				fmt.Println("share diff: ", shareDiff.String())
+				s.T().Log("share diff: ", shareDiff.String())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fmt.Println("share diff: ", shareDiff.String())
s.Require().True(shareDiff.Equal(tc.expectedShareDiff))
s.T().Log("share diff: ", shareDiff.String())
s.Require().True(shareDiff.Equal(tc.expectedShareDiff))

}
})
}
}
Comment on lines +1055 to +1142
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add test cases for multiple validators and delegations

Currently, the test function covers single validator scenarios. Consider adding test cases with multiple validators and multiple superfluid delegations to ensure comprehensive coverage.


⚠️ Potential issue

Replace math.LegacyDec with osmomath.Dec for consistency

The test function TestForceUndelegateAndBurnOsmoTokens uses math.LegacyDec and math.LegacyMustNewDecFromStr, which are deprecated. Consider updating to osmomath.Dec and osmomath.MustNewDecFromStr to maintain consistency and avoid deprecation issues.

Apply this diff to update the code:

1058,1063c1058,1063
< 		expectedShareDiff math.LegacyDec
---
> 		expectedShareDiff osmomath.Dec
1071c1071
< 			math.LegacyMustNewDecFromStr("10"),
---
> 			osmomath.MustNewDecFromStr("10"),
1079c1079
< 			math.LegacyDec{},
---
> 			osmomath.Dec{},
1087c1087
< 			math.LegacyMustNewDecFromStr("10.526315789473684210"),
---
> 			osmomath.MustNewDecFromStr("10.526315789473684210"),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (s *KeeperTestSuite) TestForceUndelegateAndBurnOsmoTokens() {
testCases := []struct {
name string
validatorStats []stakingtypes.BondStatus
superDelegations []superfluidDelegation
jailed bool
jailValWithSmallAmt bool
expectedShareDiff math.LegacyDec
}{
{
"with single validator and single superfluid delegation and single undelegation",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, 0, 0, 1000000}},
false,
false,
math.LegacyMustNewDecFromStr("10"),
},
{
"jailed validator where superfluid delegation was major delegation",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, 0, 0, 1}},
true,
true,
math.LegacyDec{},
},
{
"jailed validator where superfluid delegation was major delegation",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, 0, 0, 1000000}},
true,
false,
math.LegacyMustNewDecFromStr("10.526315789473684210"),
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
s.SetupTest()
// setup validators
valAddrs := s.SetupValidators(tc.validatorStats)
denoms, _ := s.SetupGammPoolsAndSuperfluidAssets([]osmomath.Dec{osmomath.NewDec(20), osmomath.NewDec(20)})
// setup superfluid delegations
_, intermediaryAccs, _ := s.setupSuperfluidDelegations(valAddrs, tc.superDelegations, denoms)
delegationBeforeUndelegate, err := s.App.StakingKeeper.GetDelegation(s.Ctx, intermediaryAccs[0].GetAccAddress(), valAddrs[0])
s.Require().NoError(err)
// jail the validator as part of set up
if tc.jailed {
validator, err := s.App.StakingKeeper.GetValidator(s.Ctx, valAddrs[0])
s.Require().NoError(err)
s.Ctx = s.Ctx.WithBlockHeight(100)
consAddr, err := validator.GetConsAddr()
s.Require().NoError(err)
// slash by slash factor
power := sdk.TokensToConsensusPower(validator.Tokens, sdk.DefaultPowerReduction)
// Note: this calls BeforeValidatorSlashed hook
s.handleEquivocationEvidence(s.Ctx, &evidencetypes.Equivocation{
Height: 80,
Time: time.Time{},
Power: power,
ConsensusAddress: sdk.ConsAddress(consAddr).String(),
})
val, err := s.App.StakingKeeper.GetValidatorByConsAddr(s.Ctx, consAddr)
s.Require().NoError(err)
s.Require().Equal(val.Jailed, true)
}
err = s.App.SuperfluidKeeper.ForceUndelegateAndBurnOsmoTokens(s.Ctx, math.NewInt(10), intermediaryAccs[0])
s.Require().NoError(err)
if !tc.jailValWithSmallAmt {
delegationAfterUndelegate, err := s.App.StakingKeeper.GetDelegation(s.Ctx, intermediaryAccs[0].GetAccAddress(), valAddrs[0])
s.Require().NoError(err)
shareDiff := delegationBeforeUndelegate.Shares.Sub(delegationAfterUndelegate.Shares)
fmt.Println("share diff: ", shareDiff.String())
s.Require().True(shareDiff.Equal(tc.expectedShareDiff))
}
})
}
}
func (s *KeeperTestSuite) TestForceUndelegateAndBurnOsmoTokens() {
testCases := []struct {
name string
validatorStats []stakingtypes.BondStatus
superDelegations []superfluidDelegation
jailed bool
jailValWithSmallAmt bool
expectedShareDiff osmomath.Dec
}{
{
"with single validator and single superfluid delegation and single undelegation",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, 0, 0, 1000000}},
false,
false,
osmomath.MustNewDecFromStr("10"),
},
{
"jailed validator where superfluid delegation was major delegation",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, 0, 0, 1}},
true,
true,
osmomath.Dec{},
},
{
"jailed validator where superfluid delegation was major delegation",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, 0, 0, 1000000}},
true,
false,
osmomath.MustNewDecFromStr("10.526315789473684210"),
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
s.SetupTest()
// setup validators
valAddrs := s.SetupValidators(tc.validatorStats)
denoms, _ := s.SetupGammPoolsAndSuperfluidAssets([]osmomath.Dec{osmomath.NewDec(20), osmomath.NewDec(20)})
// setup superfluid delegations
_, intermediaryAccs, _ := s.setupSuperfluidDelegations(valAddrs, tc.superDelegations, denoms)
delegationBeforeUndelegate, err := s.App.StakingKeeper.GetDelegation(s.Ctx, intermediaryAccs[0].GetAccAddress(), valAddrs[0])
s.Require().NoError(err)
// jail the validator as part of set up
if tc.jailed {
validator, err := s.App.StakingKeeper.GetValidator(s.Ctx, valAddrs[0])
s.Require().NoError(err)
s.Ctx = s.Ctx.WithBlockHeight(100)
consAddr, err := validator.GetConsAddr()
s.Require().NoError(err)
// slash by slash factor
power := sdk.TokensToConsensusPower(validator.Tokens, sdk.DefaultPowerReduction)
// Note: this calls BeforeValidatorSlashed hook
s.handleEquivocationEvidence(s.Ctx, &evidencetypes.Equivocation{
Height: 80,
Time: time.Time{},
Power: power,
ConsensusAddress: sdk.ConsAddress(consAddr).String(),
})
val, err := s.App.StakingKeeper.GetValidatorByConsAddr(s.Ctx, consAddr)
s.Require().NoError(err)
s.Require().Equal(val.Jailed, true)
}
err = s.App.SuperfluidKeeper.ForceUndelegateAndBurnOsmoTokens(s.Ctx, math.NewInt(10), intermediaryAccs[0])
s.Require().NoError(err)
if !tc.jailValWithSmallAmt {
delegationAfterUndelegate, err := s.App.StakingKeeper.GetDelegation(s.Ctx, intermediaryAccs[0].GetAccAddress(), valAddrs[0])
s.Require().NoError(err)
shareDiff := delegationBeforeUndelegate.Shares.Sub(delegationAfterUndelegate.Shares)
fmt.Println("share diff: ", shareDiff.String())
s.Require().True(shareDiff.Equal(tc.expectedShareDiff))
}
})
}
}

⚠️ Potential issue

Ensure error handling when retrieving delegations after undelegation

In the test cases where jailValWithSmallAmt is true, retrieving the delegation after undelegation may result in an error. Ensure that the error is appropriately handled to prevent unexpected test failures.

Apply this diff to handle the error condition:

-				s.Require().NoError(err)
-				shareDiff := delegationBeforeUndelegate.Shares.Sub(delegationAfterUndelegate.Shares)
-				s.Require().True(shareDiff.Equal(tc.expectedShareDiff))
+				if tc.jailValWithSmallAmt {
+					s.Require().Error(err)
+				} else {
+					s.Require().NoError(err)
+					shareDiff := delegationBeforeUndelegate.Shares.Sub(delegationAfterUndelegate.Shares)
+					s.Require().True(shareDiff.Equal(tc.expectedShareDiff))
+				}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (s *KeeperTestSuite) TestForceUndelegateAndBurnOsmoTokens() {
testCases := []struct {
name string
validatorStats []stakingtypes.BondStatus
superDelegations []superfluidDelegation
jailed bool
jailValWithSmallAmt bool
expectedShareDiff math.LegacyDec
}{
{
"with single validator and single superfluid delegation and single undelegation",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, 0, 0, 1000000}},
false,
false,
math.LegacyMustNewDecFromStr("10"),
},
{
"jailed validator where superfluid delegation was major delegation",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, 0, 0, 1}},
true,
true,
math.LegacyDec{},
},
{
"jailed validator where superfluid delegation was major delegation",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, 0, 0, 1000000}},
true,
false,
math.LegacyMustNewDecFromStr("10.526315789473684210"),
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
s.SetupTest()
// setup validators
valAddrs := s.SetupValidators(tc.validatorStats)
denoms, _ := s.SetupGammPoolsAndSuperfluidAssets([]osmomath.Dec{osmomath.NewDec(20), osmomath.NewDec(20)})
// setup superfluid delegations
_, intermediaryAccs, _ := s.setupSuperfluidDelegations(valAddrs, tc.superDelegations, denoms)
delegationBeforeUndelegate, err := s.App.StakingKeeper.GetDelegation(s.Ctx, intermediaryAccs[0].GetAccAddress(), valAddrs[0])
s.Require().NoError(err)
// jail the validator as part of set up
if tc.jailed {
validator, err := s.App.StakingKeeper.GetValidator(s.Ctx, valAddrs[0])
s.Require().NoError(err)
s.Ctx = s.Ctx.WithBlockHeight(100)
consAddr, err := validator.GetConsAddr()
s.Require().NoError(err)
// slash by slash factor
power := sdk.TokensToConsensusPower(validator.Tokens, sdk.DefaultPowerReduction)
// Note: this calls BeforeValidatorSlashed hook
s.handleEquivocationEvidence(s.Ctx, &evidencetypes.Equivocation{
Height: 80,
Time: time.Time{},
Power: power,
ConsensusAddress: sdk.ConsAddress(consAddr).String(),
})
val, err := s.App.StakingKeeper.GetValidatorByConsAddr(s.Ctx, consAddr)
s.Require().NoError(err)
s.Require().Equal(val.Jailed, true)
}
err = s.App.SuperfluidKeeper.ForceUndelegateAndBurnOsmoTokens(s.Ctx, math.NewInt(10), intermediaryAccs[0])
s.Require().NoError(err)
if !tc.jailValWithSmallAmt {
delegationAfterUndelegate, err := s.App.StakingKeeper.GetDelegation(s.Ctx, intermediaryAccs[0].GetAccAddress(), valAddrs[0])
s.Require().NoError(err)
shareDiff := delegationBeforeUndelegate.Shares.Sub(delegationAfterUndelegate.Shares)
fmt.Println("share diff: ", shareDiff.String())
s.Require().True(shareDiff.Equal(tc.expectedShareDiff))
}
})
}
}
func (s *KeeperTestSuite) TestForceUndelegateAndBurnOsmoTokens() {
testCases := []struct {
name string
validatorStats []stakingtypes.BondStatus
superDelegations []superfluidDelegation
jailed bool
jailValWithSmallAmt bool
expectedShareDiff math.LegacyDec
}{
{
"with single validator and single superfluid delegation and single undelegation",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, 0, 0, 1000000}},
false,
false,
math.LegacyMustNewDecFromStr("10"),
},
{
"jailed validator where superfluid delegation was major delegation",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, 0, 0, 1}},
true,
true,
math.LegacyDec{},
},
{
"jailed validator where superfluid delegation was major delegation",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, 0, 0, 1000000}},
true,
false,
math.LegacyMustNewDecFromStr("10.526315789473684210"),
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
s.SetupTest()
// setup validators
valAddrs := s.SetupValidators(tc.validatorStats)
denoms, _ := s.SetupGammPoolsAndSuperfluidAssets([]osmomath.Dec{osmomath.NewDec(20), osmomath.NewDec(20)})
// setup superfluid delegations
_, intermediaryAccs, _ := s.setupSuperfluidDelegations(valAddrs, tc.superDelegations, denoms)
delegationBeforeUndelegate, err := s.App.StakingKeeper.GetDelegation(s.Ctx, intermediaryAccs[0].GetAccAddress(), valAddrs[0])
s.Require().NoError(err)
// jail the validator as part of set up
if tc.jailed {
validator, err := s.App.StakingKeeper.GetValidator(s.Ctx, valAddrs[0])
s.Require().NoError(err)
s.Ctx = s.Ctx.WithBlockHeight(100)
consAddr, err := validator.GetConsAddr()
s.Require().NoError(err)
// slash by slash factor
power := sdk.TokensToConsensusPower(validator.Tokens, sdk.DefaultPowerReduction)
// Note: this calls BeforeValidatorSlashed hook
s.handleEquivocationEvidence(s.Ctx, &evidencetypes.Equivocation{
Height: 80,
Time: time.Time{},
Power: power,
ConsensusAddress: sdk.ConsAddress(consAddr).String(),
})
val, err := s.App.StakingKeeper.GetValidatorByConsAddr(s.Ctx, consAddr)
s.Require().NoError(err)
s.Require().Equal(val.Jailed, true)
}
err = s.App.SuperfluidKeeper.ForceUndelegateAndBurnOsmoTokens(s.Ctx, math.NewInt(10), intermediaryAccs[0])
s.Require().NoError(err)
if !tc.jailValWithSmallAmt {
delegationAfterUndelegate, err := s.App.StakingKeeper.GetDelegation(s.Ctx, intermediaryAccs[0].GetAccAddress(), valAddrs[0])
if tc.jailValWithSmallAmt {
s.Require().Error(err)
} else {
s.Require().NoError(err)
shareDiff := delegationBeforeUndelegate.Shares.Sub(delegationAfterUndelegate.Shares)
fmt.Println("share diff: ", shareDiff.String())
s.Require().True(shareDiff.Equal(tc.expectedShareDiff))
}
}
})
}
}

func (s *KeeperTestSuite) TestUnbondConvertAndStake() {
defaultJoinTime := s.Ctx.BlockTime()
type tc struct {
Expand Down