Skip to content

Commit

Permalink
refactor!: update staking code
Browse files Browse the repository at this point in the history
  • Loading branch information
stana-miric committed Jun 6, 2024
1 parent 75529c2 commit 009804e
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Improvements

* (staking) [#20444](https://github.com/cosmos/cosmos-sdk/pull/20444) Disable tokenization of shares from redelegations.
* (x/bank) [#18956](https://github.com/cosmos/cosmos-sdk/pull/18956) Introduced a new `DenomOwnersByQuery` query method for `DenomOwners`, which accepts the denom value as a query string parameter, resolving issues with denoms containing slashes.
* (x/gov) [#18707](https://github.com/cosmos/cosmos-sdk/pull/18707) Improve genesis validation.
* (x/auth/tx) [#18772](https://github.com/cosmos/cosmos-sdk/pull/18772) Remove misleading gas wanted from tx simulation failure log.
Expand Down
99 changes: 99 additions & 0 deletions tests/integration/staking/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,105 @@ func TestTokenizeSharesAndRedeemTokens(t *testing.T) {
}
}

func TestRedelegationTokenization(t *testing.T) {
// Test that a delegator with ongoing redelegation cannot
// tokenize any shares until the redelegation is complete.
f := initFixture(t)

ctx := f.sdkCtx
var (
stakingKeeper = f.stakingKeeper
bankKeeper = f.bankKeeper
)
msgServer := keeper.NewMsgServerImpl(stakingKeeper)
validators, err := stakingKeeper.GetAllValidators(ctx)
require.NoError(t, err)
validatorA := validators[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)
bondedDenom, err := stakingKeeper.BondDenom(ctx)
require.NoError(t, err)
delegateCoin := sdk.NewCoin(bondedDenom, delegateAmount)

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

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

redelegation, err := stakingKeeper.GetRedelegations(ctx, alice, uint16(10))
require.NoError(t, err)
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(bondedDenom, 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, err = stakingKeeper.GetRedelegations(ctx, alice, uint16(10))
require.NoError(t, err)
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
f.stakingKeeper.EndBlocker(f.sdkCtx)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// advance by 22 days
ctx = ctx.WithBlockTime(ctx.BlockTime().Add(22 * 24 * time.Hour))
// begin block
f.stakingKeeper.BeginBlocker(f.sdkCtx)
// end block
f.stakingKeeper.EndBlocker(f.sdkCtx)

// check that the redelegation is removed
redelegation, err = stakingKeeper.GetRedelegations(ctx, alice, uint16(10))
require.NoError(t, err)
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)
}

// Helper function to setup a delegator and validator for the Tokenize/Redeem conversion tests
func setupTestTokenizeAndRedeemConversion(
t *testing.T,
Expand Down
9 changes: 9 additions & 0 deletions x/staking/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,15 @@ func (k msgServer) TokenizeShares(goCtx context.Context, msg *types.MsgTokenizeS
return nil, err
}

// Check that the delegator has no ongoing redelegations to the validator
found, err := k.HasReceivingRedelegation(ctx, delegatorAddress, valAddr)
if err != nil {
return nil, err
}
if found {
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
9 changes: 9 additions & 0 deletions x/staking/simulation/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,15 @@ func SimulateMsgTokenizeShares(txGen client.TxConfig, ak types.AccountKeeper, bk
return simtypes.NoOpMsg(types.ModuleName, msgType, "tokenize shares disabled"), nil, nil
}

// Make sure that the delegator has no ongoing redelegations to the validator
found, err := k.HasReceivingRedelegation(ctx, delAddrBz, valAddr)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "error checking receiving redelegation"), nil, err
}
if found {
return simtypes.NoOpMsg(types.ModuleName, msgType, "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 @@ -67,4 +67,5 @@ var (
ErrValidatorLiquidSharesUnderflow = errors.Register(ModuleName, 117, "validator liquid shares underflow")
ErrTotalLiquidStakedUnderflow = errors.Register(ModuleName, 118, "total liquid staked underflow")
ErrTinyRedemptionAmount = errors.Register(ModuleName, 119, "too few tokens to redeem (truncates to zero tokens)")
ErrRedelegationInProgress = errors.Register(ModuleName, 120, "delegator is not allowed to tokenize shares from validator with a redelegation in progress")
)

0 comments on commit 009804e

Please sign in to comment.