Skip to content

Commit

Permalink
feat: Cancel unbonding delegation entry (#10885)
Browse files Browse the repository at this point in the history
## Description


Closes: #577

This pull request contains `Canceling unbonding delegation entry` and `delegate back to previous validator`

### `Msg` Service
```protobuf=
package cosmos.staking.v1beta1;

service Msg {
    // CancelUnbondingDelegation
    rpc CancelUnbondingDelegation(MsgCancelUnbondingDelegation) returns (MsgCancelUnbondingDelegationResponse);
}

// MsgCancelUnbondingDelegation
message MsgCancelUnbondingDelegation {
  option (gogoproto.equal)           = false;
  option (gogoproto.goproto_getters) = false;

  string                   delegator_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
  string                   validator_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
  cosmos.base.v1beta1.Coin amount            = 3 [(gogoproto.nullable) = false];
  // creation_height is the height which the unbonding took place.
  int64 creation_height = 4;
}

// MsgCancelUnbondingDelegationResponse
message MsgCancelUnbondingDelegationResponse{
}
```

### `Msg` Method Implementation
```go=
func (k msgServer) CancelUnbondingDelegation(goCtx context.Context, msg *types.MsgCancelUnbondingDelegation) (*types.MsgCancelUnbondingDelegationResponse, error) {
    /*
    // checking the unbonding delegation at creation_height 
         // get the unbonding delegations of delegatorAddress 
        if ubdEntry balance is equal to msg.Amount 
            remove the entry from ubd  
        else 
            update the specific entry with new balance
                
        if len(ubd.Entries) == 0 {
            k.RemoveUnbondingDelegation(ctx, ubd)
        } else {
            k.SetUnbondingDelegation(ctx, ubd)
        }
    */
    

    // update the delegation back to validator 
    // get validator
        validator, found := k.GetValidator(ctx, valAddr)
        if !found {
            return nil, types.ErrNoValidatorFound
        }

        // delegate the unbonding amount to validator back
        _, err = k.Keeper.Delegate(ctx, delegatorAddress, msg.Amount.Amount, types.Unbonding, validator, false)
        if err != nil {
            return nil, err
        }
    
    
        _, err := ms.Keeper.CancelUnbondingDelegation(ctx,delegatorAddress,validatorAddress,amount,creation_height)
        if err != nil {
            return nil, err 
        }


        return &types.MsgCancelUnbondingDelegationResponse{}, nil
}
```

#### `cli tx` Method
```bash=
simd tx staking cancel-unbond [validator-address] [amount] [creation_height] --from [user] --chain-id [chain-id]
Example: 
simd tx staking cancel-unbond cosmosvaloper1mqtyv4qux68r26mql2hjhgrvz72snjwpulq22m 100000stake 10280 --from test1 --chain-id test-chain
```

---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting))
- [ ] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules)
- [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [ ] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [ ] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed 
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
  • Loading branch information
gsk967 committed Apr 5, 2022
1 parent 6fa9252 commit 5bde368
Show file tree
Hide file tree
Showing 18 changed files with 2,412 additions and 171 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [\#10868](https://github.com/cosmos/cosmos-sdk/pull/10868) Bump gov to v1beta2. Both v1beta1 and v1beta2 queries and Msgs are accepted.
* [\#11011](https://github.com/cosmos/cosmos-sdk/pull/11011) Remove burning of deposits when qourum is not reached on a governance proposal and when the deposit is not fully met.
* [\#11019](https://github.com/cosmos/cosmos-sdk/pull/11019) Add `MsgCreatePermanentLockedAccount` and CLI method for creating permanent locked account
* (x/staking) [\#10885] (https://github.com/cosmos/cosmos-sdk/pull/10885) Add new `CancelUnbondingDelegation`
transaction to `x/staking` module. Delegators can now cancel unbonding delegation entry and re-delegate back to validator.
* (x/feegrant) [\#10830](https://github.com/cosmos/cosmos-sdk/pull/10830) Expired allowances will be pruned from state.
* (x/authz,x/feegrant) [\#11214](https://github.com/cosmos/cosmos-sdk/pull/11214) Fix Amino JSON encoding of authz and feegrant Msgs to be consistent with other modules.
* (authz)[\#11060](https://github.com/cosmos/cosmos-sdk/pull/11060) Support grant with no expire time.
Expand Down
1,294 changes: 1,205 additions & 89 deletions api/cosmos/staking/v1beta1/tx.pulsar.go

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions api/cosmos/staking/v1beta1/tx_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions proto/cosmos/staking/v1beta1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ service Msg {
// Undelegate defines a method for performing an undelegation from a
// delegate and a validator.
rpc Undelegate(MsgUndelegate) returns (MsgUndelegateResponse);

// CancelUnbondingDelegation defines a method for performing canceling the unbonding delegation
// and delegate back to previous validator.
rpc CancelUnbondingDelegation(MsgCancelUnbondingDelegation) returns (MsgCancelUnbondingDelegationResponse);
}

// MsgCreateValidator defines a SDK message for creating a new validator.
Expand Down Expand Up @@ -136,3 +140,20 @@ message MsgUndelegate {
message MsgUndelegateResponse {
google.protobuf.Timestamp completion_time = 1 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
}

// MsgCancelUnbondingDelegation defines the SDK message for performing a cancel unbonding delegation for delegator
message MsgCancelUnbondingDelegation{
option (cosmos.msg.v1.signer) = "delegator_address";
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

string delegator_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string validator_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// amount is always less than or equal to unbonding delegation entry balance
cosmos.base.v1beta1.Coin amount = 3 [(gogoproto.nullable) = false];
// creation_height is the height which the unbonding took place.
int64 creation_height = 4;
}

// MsgCancelUnbondingDelegationResponse
message MsgCancelUnbondingDelegationResponse{}
1 change: 1 addition & 0 deletions simapp/params/weights.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const (
DefaultWeightMsgDelegate int = 100
DefaultWeightMsgUndelegate int = 100
DefaultWeightMsgBeginRedelegate int = 100
DefaultWeightMsgCancelUnbondingDelegation int = 100

DefaultWeightCommunitySpendProposal int = 5
DefaultWeightTextProposal int = 5
Expand Down
52 changes: 52 additions & 0 deletions x/staking/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cli
import (
"fmt"
"os"
"strconv"
"strings"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -44,6 +45,7 @@ func NewTxCmd() *cobra.Command {
NewDelegateCmd(),
NewRedelegateCmd(),
NewUnbondCmd(),
NewCancelUnbondingDelegation(),
)

return stakingTxCmd
Expand Down Expand Up @@ -277,6 +279,56 @@ $ %s tx staking unbond %s1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj 100stake --from
return cmd
}

func NewCancelUnbondingDelegation() *cobra.Command {
bech32PrefixValAddr := sdk.GetConfig().GetBech32ValidatorAddrPrefix()

cmd := &cobra.Command{
Use: "cancel-unbond [validator-addr] [amount] [creation-height]",
Short: "Cancel unbonding delegation and delegate back to the validator",
Args: cobra.ExactArgs(3),
Long: strings.TrimSpace(
fmt.Sprintf(`Cancel Unbonding Delegation and delegate back to the validator.
Example:
$ %s tx staking cancel-unbond %s1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj 100stake 2 --from mykey
`,
version.AppName, bech32PrefixValAddr,
),
),
Example: fmt.Sprintf(`$ %s tx staking cancel-unbond %s1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj 100stake 2 --from mykey`,
version.AppName, bech32PrefixValAddr),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}
delAddr := clientCtx.GetFromAddress()
valAddr, err := sdk.ValAddressFromBech32(args[0])
if err != nil {
return err
}

amount, err := sdk.ParseCoinNormalized(args[1])
if err != nil {
return err
}

creationHeight, err := strconv.ParseInt(args[2], 10, 64)
if err != nil {
return sdkerrors.Wrap(fmt.Errorf("invalid height: %d", creationHeight), "invalid height")
}

msg := types.NewMsgCancelUnbondingDelegation(delAddr, valAddr, creationHeight, amount)

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}

flags.AddTxFlagsToCmd(cmd)

return cmd
}

func newBuildCreateValidatorMsg(clientCtx client.Context, txf tx.Factory, fs *flag.FlagSet) (tx.Factory, *types.MsgCreateValidator, error) {
fAmount, _ := fs.GetString(FlagAmount)
amount, err := sdk.ParseCoinNormalized(fAmount)
Expand Down
111 changes: 110 additions & 1 deletion x/staking/client/testutil/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
"github.com/cosmos/cosmos-sdk/testutil/network"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/query"
banktestutil "github.com/cosmos/cosmos-sdk/x/bank/client/testutil"
"github.com/cosmos/cosmos-sdk/x/staking/client/cli"
Expand Down Expand Up @@ -72,8 +73,11 @@ func (s *IntegrationTestSuite) SetupSuite() {
_, err = s.network.WaitForHeight(1)
s.Require().NoError(err)
// unbonding
_, err = MsgUnbondExec(val.ClientCtx, val.Address, val.ValAddress, unbond)
out, err = MsgUnbondExec(val.ClientCtx, val.Address, val.ValAddress, unbond)
s.Require().NoError(err)
s.Require().NoError(err)
s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &txRes))
s.Require().Equal(uint32(0), txRes.Code)

_, err = s.network.WaitForHeight(1)
s.Require().NoError(err)
Expand Down Expand Up @@ -1297,6 +1301,111 @@ func (s *IntegrationTestSuite) TestNewUnbondCmd() {
}
}

func (s *IntegrationTestSuite) TestNewCancelUnbondingDelegationCmd() {
val := s.network.Validators[0]

testCases := []struct {
name string
args []string
expectErr bool
expectedCode uint32
respType proto.Message
}{
{
"Without validator address",
[]string{
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
},
true, 0, nil,
},
{
"Without canceling unbond delegation amount",
[]string{
val.ValAddress.String(),
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
},
true, 0, nil,
},
{
"Without unbond creation height",
[]string{
val.ValAddress.String(),
sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(150)).String(),
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
},
true, 0, nil,
},
{
"Wrong unbonding creation height",
[]string{
val.ValAddress.String(),
sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10)).String(),
sdk.NewInt(10000).String(),
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
},
false, sdkerrors.ErrNotFound.ABCICode(), &sdk.TxResponse{},
},
{
"Invalid unbonding amount (higher than the unbonding amount)",
[]string{
val.ValAddress.String(),
sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10000)).String(),
sdk.NewInt(3).String(),
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
},
false, sdkerrors.ErrInvalidRequest.ABCICode(), &sdk.TxResponse{},
},
{
"valid transaction of canceling unbonding delegation",
[]string{
val.ValAddress.String(),
sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10)).String(),
sdk.NewInt(3).String(),
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
},
false, 0, &sdk.TxResponse{},
},
}

for _, tc := range testCases {
tc := tc

s.Run(tc.name, func() {
cmd := cli.NewCancelUnbondingDelegation()
clientCtx := val.ClientCtx

out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)
if tc.expectErr {
s.Require().Error(err)
} else {
s.Require().NoError(err, out.String())
s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String())

txResp := tc.respType.(*sdk.TxResponse)
s.Require().Equal(tc.expectedCode, txResp.Code, out.String())
}
})
}
}

// TestBlockResults tests that the validator updates correctly show when
// calling the /block_results RPC endpoint.
// ref: https://github.com/cosmos/cosmos-sdk/issues/7401.
Expand Down
2 changes: 1 addition & 1 deletion x/staking/keeper/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ func (k Keeper) DequeueAllMatureUBDQueue(ctx sdk.Context, currTime time.Time) (m
store := ctx.KVStore(k.storeKey)

// gets an iterator for all timeslices from time 0 until the current Blockheader time
unbondingTimesliceIterator := k.UBDQueueIterator(ctx, ctx.BlockHeader().Time)
unbondingTimesliceIterator := k.UBDQueueIterator(ctx, currTime)
defer unbondingTimesliceIterator.Close()

for ; unbondingTimesliceIterator.Valid(); unbondingTimesliceIterator.Next() {
Expand Down
3 changes: 3 additions & 0 deletions x/staking/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type KeeperTestSuite struct {
addrs []sdk.AccAddress
vals []types.Validator
queryClient types.QueryClient
msgServer types.MsgServer
}

func (suite *KeeperTestSuite) SetupTest() {
Expand All @@ -35,6 +36,8 @@ func (suite *KeeperTestSuite) SetupTest() {
types.RegisterQueryServer(queryHelper, querier)
queryClient := types.NewQueryClient(queryHelper)

suite.msgServer = keeper.NewMsgServerImpl(app.StakingKeeper)

addrs, _, validators := createValidators(suite.T(), ctx, app, []int64{9, 8, 7})
header := tmproto.Header{
ChainID: "HelloChain",
Expand Down

0 comments on commit 5bde368

Please sign in to comment.