From 197fcf02f4e27d086e5cc35a1ab9fda0165f1c2c Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Wed, 22 May 2024 12:49:08 +0200
Subject: [PATCH 1/7] Add testcase for disabled redelegation
---
.../staking/keeper/msg_server_test.go | 95 ++++++++++++++++++-
1 file changed, 92 insertions(+), 3 deletions(-)
diff --git a/tests/integration/staking/keeper/msg_server_test.go b/tests/integration/staking/keeper/msg_server_test.go
index 4c7336c2f52c..76f7962a2156 100644
--- a/tests/integration/staking/keeper/msg_server_test.go
+++ b/tests/integration/staking/keeper/msg_server_test.go
@@ -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"
@@ -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")
@@ -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 there 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)
+}
From cd9a32c66babc4afee0333422773885f8e1e9f86 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Wed, 22 May 2024 12:49:25 +0200
Subject: [PATCH 2/7] Fix typo
---
tests/integration/staking/keeper/msg_server_test.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/integration/staking/keeper/msg_server_test.go b/tests/integration/staking/keeper/msg_server_test.go
index 76f7962a2156..5a622ab1b749 100644
--- a/tests/integration/staking/keeper/msg_server_test.go
+++ b/tests/integration/staking/keeper/msg_server_test.go
@@ -1781,7 +1781,7 @@ func TestRedelegationTokenization(t *testing.T) {
// end block
staking.EndBlocker(ctx, stakingKeeper)
- // check that there the redelegation is removed
+ // check that the redelegation is removed
redelegation = stakingKeeper.GetRedelegations(ctx, alice, uint16(10))
require.Len(t, redelegation, 0, "expect no redelegations")
From 9e343dbd224ed41bd3645444e511fe21085cb705 Mon Sep 17 00:00:00 2001
From: Simon Noetzlin
Date: Wed, 22 May 2024 16:50:36 +0200
Subject: [PATCH 3/7] disable tokenization of shares being redelegated
---
x/staking/keeper/msg_server.go | 5 +++++
x/staking/types/errors.go | 1 +
2 files changed, 6 insertions(+)
diff --git a/x/staking/keeper/msg_server.go b/x/staking/keeper/msg_server.go
index a88af57ec750..67c31f0a8e08 100644
--- a/x/staking/keeper/msg_server.go
+++ b/x/staking/keeper/msg_server.go
@@ -716,6 +716,11 @@ func (k msgServer) TokenizeShares(goCtx context.Context, msg *types.MsgTokenizeS
return nil, err
}
+ // Check that the delegator doesn't have an ongoing redelegation 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,
diff --git a/x/staking/types/errors.go b/x/staking/types/errors.go
index a684f5898af3..338b5e147066 100644
--- a/x/staking/types/errors.go
+++ b/x/staking/types/errors.go
@@ -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 ongoing redelegation")
)
From 02358af367820e64113da40c890d1205e1dcf8c8 Mon Sep 17 00:00:00 2001
From: Simon Noetzlin
Date: Wed, 22 May 2024 17:05:22 +0200
Subject: [PATCH 4/7] nit
---
x/staking/types/errors.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x/staking/types/errors.go b/x/staking/types/errors.go
index 338b5e147066..7cb6d7c2777b 100644
--- a/x/staking/types/errors.go
+++ b/x/staking/types/errors.go
@@ -71,5 +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 ongoing redelegation")
+ ErrRedelegationInProgress = sdkerrors.Register(ModuleName, 120, "delegator is not allowed to tokenize shares from validator with a redelegation in progress")
)
From 06eb2e5d4ef0f2c2cd48628a6938c34cb8b510fa Mon Sep 17 00:00:00 2001
From: Simon Noetzlin
Date: Fri, 24 May 2024 10:15:15 +0200
Subject: [PATCH 5/7] add changelog entry
---
CHANGELOG.md | 6 ++++++
x/staking/keeper/msg_server.go | 4 ++++
2 files changed, 10 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 569fa477947f..95e5c043325f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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.11-ics-lsm
This is a special cosmos-sdk release with support for both ICS and LSM.
diff --git a/x/staking/keeper/msg_server.go b/x/staking/keeper/msg_server.go
index 67c31f0a8e08..6bc69a000877 100644
--- a/x/staking/keeper/msg_server.go
+++ b/x/staking/keeper/msg_server.go
@@ -716,7 +716,11 @@ func (k msgServer) TokenizeShares(goCtx context.Context, msg *types.MsgTokenizeS
return nil, err
}
+<<<<<<< HEAD
// Check that the delegator doesn't have an ongoing redelegation to the validator
+=======
+ // Check that the delegator has no ongoing redelegation to the validator
+>>>>>>> 37001ff9f (add changelog entry)
if k.HasReceivingRedelegation(ctx, delegatorAddress, valAddr) {
return nil, types.ErrRedelegationInProgress
}
From 1060822a6637f3bdb3b723cb448088d7f77c31ce Mon Sep 17 00:00:00 2001
From: Simon Noetzlin
Date: Fri, 24 May 2024 10:30:22 +0200
Subject: [PATCH 6/7] fix conflicts
---
x/staking/keeper/msg_server.go | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/x/staking/keeper/msg_server.go b/x/staking/keeper/msg_server.go
index 6bc69a000877..244ca25c8d85 100644
--- a/x/staking/keeper/msg_server.go
+++ b/x/staking/keeper/msg_server.go
@@ -716,11 +716,7 @@ func (k msgServer) TokenizeShares(goCtx context.Context, msg *types.MsgTokenizeS
return nil, err
}
-<<<<<<< HEAD
- // Check that the delegator doesn't have an ongoing redelegation to the validator
-=======
- // Check that the delegator has no ongoing redelegation to the validator
->>>>>>> 37001ff9f (add changelog entry)
+ // Check that the delegator has no ongoing redelegations to the validator
if k.HasReceivingRedelegation(ctx, delegatorAddress, valAddr) {
return nil, types.ErrRedelegationInProgress
}
From 27461f635a1e0f3ab78adae5aad864867d8e5816 Mon Sep 17 00:00:00 2001
From: Simon Noetzlin
Date: Fri, 24 May 2024 10:59:52 +0200
Subject: [PATCH 7/7] fix non-deterministic tests
---
x/staking/simulation/operations.go | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/x/staking/simulation/operations.go b/x/staking/simulation/operations.go
index 6522b70450cf..febaa8962643 100644
--- a/x/staking/simulation/operations.go
+++ b/x/staking/simulation/operations.go
@@ -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() {