Skip to content

Commit

Permalink
refactor!: update staking code #2 (cosmos#20444)
Browse files Browse the repository at this point in the history
Co-authored-by: Philip Offtermatt <p.offtermatt@gmail.com>
  • Loading branch information
sainoe and p-offtermatt committed May 24, 2024
1 parent c059f66 commit cdc3845
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 4 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ Ref: https://keepachangelog.com/en/1.0.0/

# Changelog

## [Unreleased]

### Improvements

* (staking) [#20444](https://github.com/cosmos/cosmos-sdk/pull/20444) Disable tokenization of shares from redelegations.

## v0.47.13-ics-lsm

This is a special cosmos-sdk release with support for both ICS and LSM.
Expand All @@ -49,7 +55,6 @@ This is a special cosmos-sdk release with support for both ICS and LSM.

* (crypto) [#20073](https://github.com/cosmos/cosmos-sdk/pull/20073) Add secp256r1 parsing support (backport from main)


## v0.47.11-ics-lsm

This is a special cosmos-sdk release with support for both ICS and LSM.
Expand Down
95 changes: 92 additions & 3 deletions tests/integration/staking/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
banktestutil "github.com/cosmos/cosmos-sdk/x/bank/testutil"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/cosmos/cosmos-sdk/x/staking/keeper"
"github.com/cosmos/cosmos-sdk/x/staking/testutil"
"github.com/cosmos/cosmos-sdk/x/staking/types"
Expand Down Expand Up @@ -161,9 +162,7 @@ func TestCancelUnbondingDelegation(t *testing.T) {

func TestTokenizeSharesAndRedeemTokens(t *testing.T) {
_, app, ctx := createTestInput(t)
var (
stakingKeeper = app.StakingKeeper
)
stakingKeeper := app.StakingKeeper

liquidStakingCapStrict := sdk.ZeroDec()
liquidStakingCapConservative := sdk.MustNewDecFromStr("0.8")
Expand Down Expand Up @@ -1712,3 +1711,93 @@ func createICAAccount(ctx sdk.Context, ak accountkeeper.AccountKeeper) sdk.AccAd

return icaAddress
}

func TestRedelegationTokenization(t *testing.T) {
// Test that a delegator with ongoing redelegation cannot
// tokenize any shares until the redelegation is complete.
_, app, ctx := createTestInput(t)
var (
stakingKeeper = app.StakingKeeper
bankKeeper = app.BankKeeper
)
msgServer := keeper.NewMsgServerImpl(stakingKeeper)
validatorA := stakingKeeper.GetAllValidators(ctx)[0]
validatorAAddress := validatorA.GetOperator()
_, validatorBAddress := setupTestTokenizeAndRedeemConversion(t, *stakingKeeper, bankKeeper, ctx)

addrs := simtestutil.AddTestAddrs(bankKeeper, stakingKeeper, ctx, 2, stakingKeeper.TokensFromConsensusPower(ctx, 10000))
alice := addrs[0]

delegateAmount := sdk.TokensFromConsensusPower(10, sdk.DefaultPowerReduction)
delegateCoin := sdk.NewCoin(stakingKeeper.BondDenom(ctx), delegateAmount)

// Alice delegates to validatorA
_, err := msgServer.Delegate(sdk.WrapSDKContext(ctx), &types.MsgDelegate{
DelegatorAddress: alice.String(),
ValidatorAddress: validatorAAddress.String(),
Amount: delegateCoin,
})

// Alice redelegates to validatorB
redelegateAmount := sdk.TokensFromConsensusPower(5, sdk.DefaultPowerReduction)
redelegateCoin := sdk.NewCoin(stakingKeeper.BondDenom(ctx), redelegateAmount)
_, err = msgServer.BeginRedelegate(sdk.WrapSDKContext(ctx), &types.MsgBeginRedelegate{
DelegatorAddress: alice.String(),
ValidatorSrcAddress: validatorAAddress.String(),
ValidatorDstAddress: validatorBAddress.String(),
Amount: redelegateCoin,
})
require.NoError(t, err)

redelegation := stakingKeeper.GetRedelegations(ctx, alice, uint16(10))
require.Len(t, redelegation, 1, "expect one redelegation")
require.Len(t, redelegation[0].Entries, 1, "expect one redelegation entry")

// Alice attempts to tokenize the redelegation, but this fails because the redelegation is ongoing
tokenizedAmount := sdk.TokensFromConsensusPower(5, sdk.DefaultPowerReduction)
tokenizedCoin := sdk.NewCoin(stakingKeeper.BondDenom(ctx), tokenizedAmount)
_, err = msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &types.MsgTokenizeShares{
DelegatorAddress: alice.String(),
ValidatorAddress: validatorBAddress.String(),
Amount: tokenizedCoin,
TokenizedShareOwner: alice.String(),
})
require.Error(t, err)
require.Equal(t, types.ErrRedelegationInProgress, err)

// Check that the redelegation is still present
redelegation = stakingKeeper.GetRedelegations(ctx, alice, uint16(10))
require.Len(t, redelegation, 1, "expect one redelegation")
require.Len(t, redelegation[0].Entries, 1, "expect one redelegation entry")

// advance time until the redelegations should mature
// end block
staking.EndBlocker(ctx, stakingKeeper)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// advance by 22 days
ctx = ctx.WithBlockTime(ctx.BlockTime().Add(22 * 24 * time.Hour))
// begin block
staking.BeginBlocker(ctx, stakingKeeper)
// end block
staking.EndBlocker(ctx, stakingKeeper)

// check that the redelegation is removed
redelegation = stakingKeeper.GetRedelegations(ctx, alice, uint16(10))
require.Len(t, redelegation, 0, "expect no redelegations")

// Alice attempts to tokenize the redelegation again, and this time it should succeed
// because there is no ongoing redelegation
_, err = msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &types.MsgTokenizeShares{
DelegatorAddress: alice.String(),
ValidatorAddress: validatorBAddress.String(),
Amount: tokenizedCoin,
TokenizedShareOwner: alice.String(),
})
require.NoError(t, err)

// Check that the tokenization was successful
shareRecord, err := stakingKeeper.GetTokenizeShareRecord(ctx, stakingKeeper.GetLastTokenizeShareRecordID(ctx))
require.NoError(t, err, "expect to find token share record")
require.Equal(t, alice.String(), shareRecord.Owner)
require.Equal(t, validatorBAddress.String(), shareRecord.Validator)
}
5 changes: 5 additions & 0 deletions x/staking/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,11 @@ func (k msgServer) TokenizeShares(goCtx context.Context, msg *types.MsgTokenizeS
return nil, err
}

// Check that the delegator has no ongoing redelegations to the validator
if k.HasReceivingRedelegation(ctx, delegatorAddress, valAddr) {
return nil, types.ErrRedelegationInProgress
}

// If this tokenization is NOT from a liquid staking provider,
// confirm it does not exceed the global and validator liquid staking cap
// If the tokenization is from a liquid staking provider,
Expand Down
5 changes: 5 additions & 0 deletions x/staking/simulation/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,11 @@ func SimulateMsgTokenizeShares(ak types.AccountKeeper, bk types.BankKeeper, k *k
return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgTokenizeShares, "tokenize shares disabled"), nil, nil
}

// Make sure that the delegator has no ongoing redelegations to the validator
if k.HasReceivingRedelegation(ctx, delAddr, srcAddr) {
return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgTokenizeShares, "delegator has redelegations in progress"), nil, nil
}

// get random destination validator
totalBond := validator.TokensFromShares(delegation.GetShares()).TruncateInt()
if !totalBond.IsPositive() {
Expand Down
1 change: 1 addition & 0 deletions x/staking/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,5 @@ var (
ErrValidatorLiquidSharesUnderflow = sdkerrors.Register(ModuleName, 117, "validator liquid shares underflow")
ErrTotalLiquidStakedUnderflow = sdkerrors.Register(ModuleName, 118, "total liquid staked underflow")
ErrTinyRedemptionAmount = sdkerrors.Register(ModuleName, 119, "too few tokens to redeem (truncates to zero tokens)")
ErrRedelegationInProgress = sdkerrors.Register(ModuleName, 120, "delegator is not allowed to tokenize shares from validator with a redelegation in progress")
)

0 comments on commit cdc3845

Please sign in to comment.