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

chore!: lsm redelegation follow-up #22538

Draft
wants to merge 5 commits into
base: feature/v0.50.x-lsm
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
114 changes: 114 additions & 0 deletions tests/integration/staking/keeper/delegation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,3 +293,117 @@ func TestValidatorBondRedelegate(t *testing.T) {
validator, _ = f.stakingKeeper.GetValidator(ctx, addrVals[0])
require.Equal(t, validator.ValidatorBondShares, math.LegacyZeroDec())
}

func TestSendTokenizedSharesToValidatorBondedAndRedeem(t *testing.T) {
t.Parallel()
f := initFixture(t)
ctx := f.sdkCtx

addrDels := simtestutil.AddTestAddrs(f.bankKeeper, f.stakingKeeper, ctx, 2, f.stakingKeeper.TokensFromConsensusPower(ctx, 10000))

addrVals := simtestutil.ConvertAddrsToValAddrs(addrDels)

startTokens := f.stakingKeeper.TokensFromConsensusPower(ctx, 10)

bondDenom, err := f.stakingKeeper.BondDenom(ctx)
require.NoError(t, err)
notBondedPool := f.stakingKeeper.GetNotBondedPool(ctx)

require.NoError(t, banktestutil.FundModuleAccount(ctx, f.bankKeeper, notBondedPool.GetName(), sdk.NewCoins(sdk.NewCoin(bondDenom, startTokens))))
f.accountKeeper.SetModuleAccount(ctx, notBondedPool)

// create a validator and a delegator to that validator
validator := testutil.NewValidator(t, addrVals[0], PKs[0])
validator.Status = types.Bonded
f.stakingKeeper.SetValidator(ctx, validator)

// set validator bond factor
params, err := f.stakingKeeper.GetParams(ctx)
require.NoError(t, err)
params.ValidatorBondFactor = math.LegacyNewDec(1)
f.stakingKeeper.SetParams(ctx, params)

// convert to validator self-bond
msgServer := keeper.NewMsgServerImpl(f.stakingKeeper)

validator, _ = f.stakingKeeper.GetValidator(ctx, addrVals[0])
err = delegateCoinsFromAccount(ctx, *f.stakingKeeper, addrDels[0], startTokens, validator)
require.NoError(t, err)
_, err = msgServer.ValidatorBond(ctx, &types.MsgValidatorBond{
DelegatorAddress: addrDels[0].String(),
ValidatorAddress: addrVals[0].String(),
})
require.NoError(t, err)

// confirm that the delegation is marked as validator bond
delegation1, err := f.stakingKeeper.GetDelegation(ctx, addrDels[0], addrVals[0])
require.NoError(t, err)
require.True(t, delegation1.ValidatorBond)

// tokenize share for 2nd account delegation
validator, _ = f.stakingKeeper.GetValidator(ctx, addrVals[0])
err = delegateCoinsFromAccount(ctx, *f.stakingKeeper, addrDels[1], startTokens, validator)
require.NoError(t, err)
// confirm that the delegation was NOT marked as validator bond
delegation2, err := f.stakingKeeper.GetDelegation(ctx, addrDels[1], addrVals[0])
require.NoError(t, err)
require.False(t, delegation2.ValidatorBond)

// confirm that the ValidatorBond delegation cannot be tokenized
_, err = msgServer.TokenizeShares(ctx, &types.MsgTokenizeShares{
DelegatorAddress: addrDels[0].String(),
ValidatorAddress: addrVals[0].String(),
TokenizedShareOwner: addrDels[0].String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, startTokens),
})
require.Error(t, err)
require.EqualError(t, err, "validator bond delegation is not allowed to tokenize share")

// tokenize share for 2nd account delegation
tokenizeShareResp, err := msgServer.TokenizeShares(ctx, &types.MsgTokenizeShares{
DelegatorAddress: addrDels[1].String(),
TokenizedShareOwner: addrDels[1].String(),
ValidatorAddress: addrVals[0].String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, startTokens),
})
require.NoError(t, err)

// transfer tokenized shares (as coins) to the delegator with validator bond
err = f.bankKeeper.SendCoins(ctx, addrDels[1], addrDels[0], sdk.NewCoins(tokenizeShareResp.Amount))
require.NoError(t, err)

// confirm that the tokenized shares are now owned by the delegator with validator bond
balanceSender := f.bankKeeper.GetBalance(ctx, addrDels[1], tokenizeShareResp.Amount.Denom)
require.Equal(t, balanceSender.Amount, math.ZeroInt())
balanceReceiver := f.bankKeeper.GetBalance(ctx, addrDels[0], tokenizeShareResp.Amount.Denom)
require.Equal(t, balanceReceiver.Amount, tokenizeShareResp.Amount.Amount)

// redeem shares and assert that the validator bond shares are increased
validator, _ = f.stakingKeeper.GetValidator(ctx, addrVals[0])
beforeRedeemShares := validator.ValidatorBondShares

redeemResp, err := msgServer.RedeemTokensForShares(ctx, &types.MsgRedeemTokensForShares{
DelegatorAddress: addrDels[0].String(),
Amount: tokenizeShareResp.Amount,
})
require.NoError(t, err)

validator, _ = f.stakingKeeper.GetValidator(ctx, addrVals[0])
afterRedeemShares := validator.ValidatorBondShares
require.True(t, afterRedeemShares.GT(beforeRedeemShares))
require.Equal(t, afterRedeemShares, beforeRedeemShares.Add(math.LegacyNewDecFromInt(tokenizeShareResp.Amount.Amount)))

// undelegate the delegator with ValidatorBond and assert that the validator bond shares are decreased
_, err = msgServer.Undelegate(ctx, &types.MsgUndelegate{
DelegatorAddress: addrDels[0].String(),
ValidatorAddress: addrVals[0].String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, redeemResp.Amount.Amount),
})
require.NoError(t, err)

validator, _ = f.stakingKeeper.GetValidator(ctx, addrVals[0])
afterUndelegateShares := validator.ValidatorBondShares
require.True(t, afterUndelegateShares.LT(afterRedeemShares))
require.Equal(t, afterUndelegateShares, afterRedeemShares.Sub(math.LegacyNewDecFromInt(redeemResp.Amount.Amount)))
require.Equal(t, afterUndelegateShares, beforeRedeemShares)
}
4 changes: 2 additions & 2 deletions x/staking/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -856,10 +856,10 @@ $ %s tx staking enable-tokenize-shares --from mykey
func NewValidatorBondCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "validator-bond [validator]",
Short: "Mark a delegation as a validator self-bond",
Short: "Mark a delegation as a validator bond.",
Args: cobra.ExactArgs(1),
Long: strings.TrimSpace(
fmt.Sprintf(`Mark a delegation as a validator self-bond.
fmt.Sprintf(`Mark a delegation as a validator bond. This can be done by any delegator but it is recommended to be done from the validator's delegation address.
Example:
$ %s tx staking validator-bond cosmosvaloper13h5xdxhsdaugwdrkusf8lkgu406h8t62jkqv3h --from mykey
Expand Down
60 changes: 48 additions & 12 deletions x/staking/keeper/liquid_stake.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
panic(err)
}

store.Set(types.TotalLiquidStakedTokensKey, tokensBz)
err = store.Set(types.TotalLiquidStakedTokensKey, tokensBz)
if err != nil {
panic(err)

Check warning

Code scanning / CodeQL

Panic in BeginBock or EndBlock consensus methods Warning

Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt
}
}

// GetTotalLiquidStakedTokens returns the total outstanding tokens owned by a liquid staking provider
Expand Down Expand Up @@ -204,7 +207,10 @@

// Increment the validator's liquid shares
validator.LiquidShares = validator.LiquidShares.Add(shares)
k.SetValidator(ctx, validator)
err = k.SetValidator(ctx, validator)
if err != nil {
return types.Validator{}, err
}

return validator, nil
}
Expand All @@ -221,7 +227,10 @@
}

validator.LiquidShares = validator.LiquidShares.Sub(shares)
k.SetValidator(ctx, validator)
err = k.SetValidator(ctx, validator)
if err != nil {
return types.Validator{}, err
}

return validator, nil
}
Expand All @@ -235,7 +244,10 @@
}

validator.ValidatorBondShares = validator.ValidatorBondShares.Add(shares)
k.SetValidator(ctx, validator)
err = k.SetValidator(ctx, validator)
if err != nil {
return err
}

return nil
}
Expand Down Expand Up @@ -264,7 +276,10 @@

// Decrement the validator's self bond
validator.ValidatorBondShares = validator.ValidatorBondShares.Sub(shares)
k.SetValidator(ctx, validator)
err = k.SetValidator(ctx, validator)
if err != nil {
return err
}

return nil
}
Expand All @@ -276,21 +291,30 @@
func (k Keeper) AddTokenizeSharesLock(ctx context.Context, address sdk.AccAddress) {
store := k.storeService.OpenKVStore(ctx)
key := types.GetTokenizeSharesLockKey(address)
store.Set(key, sdk.FormatTimeBytes(time.Time{}))
err := store.Set(key, sdk.FormatTimeBytes(time.Time{}))
if err != nil {
panic(err)
}
}

// Removes the tokenize share lock for an account to enable tokenizing shares
func (k Keeper) RemoveTokenizeSharesLock(ctx context.Context, address sdk.AccAddress) {
store := k.storeService.OpenKVStore(ctx)
key := types.GetTokenizeSharesLockKey(address)
store.Delete(key)
err := store.Delete(key)
if err != nil {
panic(err)

Check warning

Code scanning / CodeQL

Panic in BeginBock or EndBlock consensus methods Warning

Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt
}
}

// Updates the timestamp associated with a lock to the time at which the lock expires
func (k Keeper) SetTokenizeSharesUnlockTime(ctx context.Context, address sdk.AccAddress, completionTime time.Time) {
store := k.storeService.OpenKVStore(ctx)
key := types.GetTokenizeSharesLockKey(address)
store.Set(key, sdk.FormatTimeBytes(completionTime))
err := store.Set(key, sdk.FormatTimeBytes(completionTime))
if err != nil {
panic(err)
}
}

// Checks if there is currently a tokenize share lock for a given account
Expand Down Expand Up @@ -356,7 +380,10 @@
store := k.storeService.OpenKVStore(ctx)
timeKey := types.GetTokenizeShareAuthorizationTimeKey(completionTime)
bz := k.cdc.MustMarshal(&authorizations)
store.Set(timeKey, bz)
err := store.Set(timeKey, bz)
if err != nil {
panic(err)
}
}

// Returns a list of addresses pending tokenize share unlocking at the same time
Expand Down Expand Up @@ -443,7 +470,10 @@

// delete unlocked addresses keys
for _, k := range keys {
store.Delete(k)
err := store.Delete(k)
if err != nil {
panic(err)

Check warning

Code scanning / CodeQL

Panic in BeginBock or EndBlock consensus methods Warning

Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt
}
}

// remove the lock from each unlocked address
Expand Down Expand Up @@ -473,7 +503,10 @@
// First reset each validator's liquid shares to 0
for _, validator := range validators {
validator.LiquidShares = math.LegacyZeroDec()
k.SetValidator(ctx, validator)
err = k.SetValidator(ctx, validator)
if err != nil {
return err
}
}

delegations, err := k.GetAllDelegations(ctx)
Expand Down Expand Up @@ -507,7 +540,10 @@
liquidTokens := validator.TokensFromShares(liquidShares).TruncateInt()

validator.LiquidShares = validator.LiquidShares.Add(liquidShares)
k.SetValidator(ctx, validator)
err = k.SetValidator(ctx, validator)
if err != nil {
return err
}

totalLiquidStakedTokens = totalLiquidStakedTokens.Add(liquidTokens)
}
Expand Down
47 changes: 41 additions & 6 deletions x/staking/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ func (k msgServer) BeginRedelegate(ctx context.Context, msg *types.MsgBeginRedel
}

// If this is a validator self-bond, the new liquid delegation cannot fall below the self-bond * bond factor
// The delegation on the new validator will not a validator bond
// The delegation on the new validator will not be a validator bond
if srcDelegation.ValidatorBond {
if err := k.SafelyDecreaseValidatorBond(ctx, valSrcAddr, srcShares); err != nil {
return nil, err
Expand Down Expand Up @@ -750,7 +750,11 @@ func (k msgServer) UnbondValidator(ctx context.Context, msg *types.MsgUnbondVali
return nil, err
}

k.jailValidator(ctx, validator)
err = k.jailValidator(ctx, validator)
if err != nil {
return nil, err
}

return &types.MsgUnbondValidatorResponse{}, nil
}

Expand Down Expand Up @@ -796,6 +800,7 @@ func (k msgServer) TokenizeShares(goCtx context.Context, msg *types.MsgTokenizeS
return nil, err
}

// ValidatorBond delegation is not allowed for tokenize share
if delegation.ValidatorBond {
return nil, types.ErrValidatorBondNotAllowedForTokenizeShare
}
Expand Down Expand Up @@ -830,6 +835,11 @@ func (k msgServer) TokenizeShares(goCtx context.Context, msg *types.MsgTokenizeS
return nil, err
}

// sanity check to avoid creating a tokenized share record with zero shares
if shares.IsZero() {
return nil, errorsmod.Wrap(types.ErrInsufficientShares, "cannot tokenize zero shares")
}

// Check that the delegator has no ongoing redelegations to the validator
found, err := k.HasReceivingRedelegation(ctx, delegatorAddress, valAddr)
if err != nil {
Expand Down Expand Up @@ -871,7 +881,10 @@ func (k msgServer) TokenizeShares(goCtx context.Context, msg *types.MsgTokenizeS
}

if validator.IsBonded() {
k.bondedTokensToNotBonded(ctx, returnAmount)
err = k.bondedTokensToNotBonded(ctx, returnAmount)
if err != nil {
return nil, err
}
}

// Note: UndelegateCoinsFromModuleToAccount is internally calling TrackUndelegation for vesting account
Expand Down Expand Up @@ -1012,7 +1025,10 @@ func (k msgServer) RedeemTokensForShares(goCtx context.Context, msg *types.MsgRe
}

if validator.IsBonded() {
k.bondedTokensToNotBonded(ctx, returnAmount)
err = k.bondedTokensToNotBonded(ctx, returnAmount)
if err != nil {
return nil, err
}
}

// Note: since delegation object has been changed from unbond call, it gets latest delegation
Expand Down Expand Up @@ -1068,6 +1084,19 @@ func (k msgServer) RedeemTokensForShares(goCtx context.Context, msg *types.MsgRe
return nil, err
}

// tokenized shares can be transferred from a validator that does not have validator bond to a delegator with validator bond
// in that case we need to increase the validator bond shares (same as during msgServer.Delegate)
newDelegation, err := k.GetDelegation(ctx, delegatorAddress, valAddr)
if err != nil {
return nil, err
}

if newDelegation.ValidatorBond {
if err := k.IncreaseValidatorBondShares(ctx, valAddr, shares); err != nil {
return nil, err
}
}

ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeRedeemShares,
Expand Down Expand Up @@ -1208,9 +1237,15 @@ func (k msgServer) ValidatorBond(goCtx context.Context, msg *types.MsgValidatorB

if !delegation.ValidatorBond {
delegation.ValidatorBond = true
k.SetDelegation(ctx, delegation)
err = k.SetDelegation(ctx, delegation)
if err != nil {
return nil, err
}
validator.ValidatorBondShares = validator.ValidatorBondShares.Add(delegation.Shares)
k.SetValidator(ctx, validator)
err = k.SetValidator(ctx, validator)
if err != nil {
return nil, err
}

ctx.EventManager().EmitEvent(
sdk.NewEvent(
Expand Down
Loading
Loading