Skip to content

Commit

Permalink
Merge pull request cosmos#42 from regen-network/clevinson/add-permane…
Browse files Browse the repository at this point in the history
…nt-locked-acct-msg

feat!: add MsgCreatePermanentLockedVestingAccount
  • Loading branch information
clevinson authored Aug 17, 2021
2 parents 3f8596c + 70b40b4 commit 30db931
Show file tree
Hide file tree
Showing 12 changed files with 797 additions and 30 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ Ref: https://keepachangelog.com/en/1.0.0/

# Changelog

## v0.43.0-regen-1

### Features

- [#42](https://github.com/regen-network/cosmos-sdk/pull/42) Add `MsgCreatePermanentLockedAccount` and CLI method
for creating permanent locked account

## [v0.43.0](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.43.0) - 2021-08-10

### Features
Expand Down
31 changes: 31 additions & 0 deletions docs/core/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,8 @@
- [Query](#cosmos.upgrade.v1beta1.Query)

- [cosmos/vesting/v1beta1/tx.proto](#cosmos/vesting/v1beta1/tx.proto)
- [MsgCreatePermanentLockedAccount](#cosmos.vesting.v1beta1.MsgCreatePermanentLockedAccount)
- [MsgCreatePermanentLockedAccountResponse](#cosmos.vesting.v1beta1.MsgCreatePermanentLockedAccountResponse)
- [MsgCreateVestingAccount](#cosmos.vesting.v1beta1.MsgCreateVestingAccount)
- [MsgCreateVestingAccountResponse](#cosmos.vesting.v1beta1.MsgCreateVestingAccountResponse)

Expand Down Expand Up @@ -8070,6 +8072,34 @@ Query defines the gRPC upgrade querier service.



<a name="cosmos.vesting.v1beta1.MsgCreatePermanentLockedAccount"></a>

### MsgCreatePermanentLockedAccount
MsgCreatePermanentLockedAccount defines a message that enables creating a permanent
locked account.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `from_address` | [string](#string) | | |
| `to_address` | [string](#string) | | |
| `amount` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | |






<a name="cosmos.vesting.v1beta1.MsgCreatePermanentLockedAccountResponse"></a>

### MsgCreatePermanentLockedAccountResponse
MsgCreatePermanentLockedAccountResponse defines the Msg/CreatePermanentLockedAccount response type.






<a name="cosmos.vesting.v1beta1.MsgCreateVestingAccount"></a>

### MsgCreateVestingAccount
Expand Down Expand Up @@ -8114,6 +8144,7 @@ Msg defines the bank Msg service.
| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint |
| ----------- | ------------ | ------------- | ------------| ------- | -------- |
| `CreateVestingAccount` | [MsgCreateVestingAccount](#cosmos.vesting.v1beta1.MsgCreateVestingAccount) | [MsgCreateVestingAccountResponse](#cosmos.vesting.v1beta1.MsgCreateVestingAccountResponse) | CreateVestingAccount defines a method that enables creating a vesting account. | |
| `CreatePermanentLockedAccount` | [MsgCreatePermanentLockedAccount](#cosmos.vesting.v1beta1.MsgCreatePermanentLockedAccount) | [MsgCreatePermanentLockedAccountResponse](#cosmos.vesting.v1beta1.MsgCreatePermanentLockedAccountResponse) | CreatePermanentLockedAccount defines a method that enables creating a permanent locked account. | |

<!-- end services -->

Expand Down
2 changes: 1 addition & 1 deletion docs/core/upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ if err != nil {
if upgradeInfo.Name == "my-plan" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) {
storeUpgrades := storetypes.StoreUpgrades{
// add store upgrades for new modules
// Example:
// Example:
// Added: []string{"foo", "bar"},
// ...
}
Expand Down
19 changes: 18 additions & 1 deletion proto/cosmos/vesting/v1beta1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ service Msg {
// CreateVestingAccount defines a method that enables creating a vesting
// account.
rpc CreateVestingAccount(MsgCreateVestingAccount) returns (MsgCreateVestingAccountResponse);
// CreatePermanentLockedAccount defines a method that enables creating a permanent
// locked account.
rpc CreatePermanentLockedAccount(MsgCreatePermanentLockedAccount) returns (MsgCreatePermanentLockedAccountResponse);
}

// MsgCreateVestingAccount defines a message that enables creating a vesting
Expand All @@ -28,4 +31,18 @@ message MsgCreateVestingAccount {
}

// MsgCreateVestingAccountResponse defines the Msg/CreateVestingAccount response type.
message MsgCreateVestingAccountResponse {}
message MsgCreateVestingAccountResponse {}

// MsgCreatePermanentLockedAccount defines a message that enables creating a permanent
// locked account.
message MsgCreatePermanentLockedAccount {
option (gogoproto.equal) = true;

string from_address = 1 [(gogoproto.moretags) = "yaml:\"from_address\""];
string to_address = 2 [(gogoproto.moretags) = "yaml:\"to_address\""];
repeated cosmos.base.v1beta1.Coin amount = 3
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
}

// MsgCreatePermanentLockedAccountResponse defines the Msg/CreatePermanentLockedAccount response type.
message MsgCreatePermanentLockedAccountResponse {}
37 changes: 37 additions & 0 deletions x/auth/vesting/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func GetTxCmd() *cobra.Command {

txCmd.AddCommand(
NewMsgCreateVestingAccountCmd(),
NewMsgCreatePermanentLockedAccountCmd(),
)

return txCmd
Expand Down Expand Up @@ -79,3 +80,39 @@ timestamp.`,

return cmd
}

// NewMsgCreatePermanentLockedAccountCmd returns a CLI command handler for creating a
// MsgCreatePermanentLockedAccount transaction.
func NewMsgCreatePermanentLockedAccountCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "create-permanent-locked-account [to_address] [amount]",
Short: "Create a new permanently locked account funded with an allocation of tokens.",
Long: `Create a new account funded with an allocation of permanently locked tokens. These
tokens may be used for staking but are non-transferable. Staking rewards will acrue as liquid and transferable
tokens.`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}
toAddr, err := sdk.AccAddressFromBech32(args[0])
if err != nil {
return err
}

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

msg := types.NewMsgCreatePermanentLockedAccount(clientCtx.GetFromAddress(), toAddr, amount)

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

flags.AddTxFlagsToCmd(cmd)

return cmd
}
2 changes: 1 addition & 1 deletion x/auth/vesting/client/testutil/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ func TestIntegrationTestSuite(t *testing.T) {
cfg := network.DefaultConfig()
cfg.NumValidators = 1
suite.Run(t, NewIntegrationTestSuite(cfg))
}
}
65 changes: 65 additions & 0 deletions x/auth/vesting/client/testutil/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,68 @@ func (s *IntegrationTestSuite) TestNewMsgCreateVestingAccountCmd() {
})
}
}
func (s *IntegrationTestSuite) TestNewMsgCreatePermanentLockedAccountCmd() {
val := s.network.Validators[0]

testCases := map[string]struct {
args []string
expectErr bool
expectedCode uint32
respType proto.Message
}{
"create a permanent locked account": {
args: []string{
sdk.AccAddress("addr4_______________").String(),
sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(100))).String(),
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address),
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()),
},
expectErr: false,
expectedCode: 0,
respType: &sdk.TxResponse{},
},
"invalid address": {
args: []string{
sdk.AccAddress("addr4").String(),
sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String(),
"4070908800",
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address),
},
expectErr: true,
expectedCode: 0,
respType: &sdk.TxResponse{},
},
"invalid coins": {
args: []string{
sdk.AccAddress("addr4_______________").String(),
"fooo",
"4070908800",
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address),
},
expectErr: true,
expectedCode: 0,
respType: &sdk.TxResponse{},
},
}

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

s.Run(name, func() {
clientCtx := val.ClientCtx

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

txResp := tc.respType.(*sdk.TxResponse)
s.Require().Equal(tc.expectedCode, txResp.Code)
}
})
}
}
3 changes: 3 additions & 0 deletions x/auth/vesting/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ func NewHandler(ak keeper.AccountKeeper, bk types.BankKeeper) sdk.Handler {
case *types.MsgCreateVestingAccount:
res, err := msgServer.CreateVestingAccount(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgCreatePermanentLockedAccount:
res, err := msgServer.CreatePermanentLockedAccount(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)

default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", types.ModuleName, msg)
Expand Down
65 changes: 65 additions & 0 deletions x/auth/vesting/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,68 @@ func (s msgServer) CreateVestingAccount(goCtx context.Context, msg *types.MsgCre

return &types.MsgCreateVestingAccountResponse{}, nil
}
func (s msgServer) CreatePermanentLockedAccount(goCtx context.Context, msg *types.MsgCreatePermanentLockedAccount) (*types.MsgCreatePermanentLockedAccountResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
ak := s.AccountKeeper
bk := s.BankKeeper

if err := bk.IsSendEnabledCoins(ctx, msg.Amount...); err != nil {
return nil, err
}

from, err := sdk.AccAddressFromBech32(msg.FromAddress)
if err != nil {
return nil, err
}
to, err := sdk.AccAddressFromBech32(msg.ToAddress)
if err != nil {
return nil, err
}

if bk.BlockedAddr(to) {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", msg.ToAddress)
}

if acc := ak.GetAccount(ctx, to); acc != nil {
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "account %s already exists", msg.ToAddress)
}

baseAccountI := ak.NewAccountWithAddress(ctx, to)

baseAcc, ok := baseAccountI.(*authtypes.BaseAccount)
if !ok {
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid account type; expected: BaseAccount, got: %T", baseAccountI)
}

var acc authtypes.AccountI = types.NewPermanentLockedAccount(baseAcc, msg.Amount)

ak.SetAccount(ctx, acc)

defer func() {
telemetry.IncrCounter(1, "new", "account")

for _, a := range msg.Amount {
if a.Amount.IsInt64() {
telemetry.SetGaugeWithLabels(
[]string{"tx", "msg", "create_permanent_locked_account"},
float32(a.Amount.Int64()),
[]metrics.Label{telemetry.NewLabel("denom", a.Denom)},
)
}
}
}()

err = bk.SendCoins(ctx, from, to, msg.Amount)
if err != nil {
return nil, err
}

ctx.EventManager().EmitEvent(
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
),
)

return &types.MsgCreatePermanentLockedAccountResponse{}, nil
}
3 changes: 3 additions & 0 deletions x/auth/vesting/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
cdc.RegisterConcrete(&DelayedVestingAccount{}, "cosmos-sdk/DelayedVestingAccount", nil)
cdc.RegisterConcrete(&PeriodicVestingAccount{}, "cosmos-sdk/PeriodicVestingAccount", nil)
cdc.RegisterConcrete(&PermanentLockedAccount{}, "cosmos-sdk/PermanentLockedAccount", nil)
cdc.RegisterConcrete(&MsgCreateVestingAccount{}, "cosmos-sdk/MsgCreateVestingAccount", nil)
cdc.RegisterConcrete(&MsgCreatePermanentLockedAccount{}, "cosmos-sdk/MsgCreatePermLockedAccount", nil)
}

// RegisterInterface associates protoName with AccountI and VestingAccount
Expand Down Expand Up @@ -53,6 +55,7 @@ func RegisterInterfaces(registry types.InterfaceRegistry) {
registry.RegisterImplementations(
(*sdk.Msg)(nil),
&MsgCreateVestingAccount{},
&MsgCreatePermanentLockedAccount{},
)

msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
Expand Down
53 changes: 53 additions & 0 deletions x/auth/vesting/types/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ import (
// TypeMsgCreateVestingAccount defines the type value for a MsgCreateVestingAccount.
const TypeMsgCreateVestingAccount = "msg_create_vesting_account"

// TypeMsgCreatePermanentLockedAccount defines the type value for a MsgCreatePermanentLockedAccount.
const TypeMsgCreatePermanentLockedAccount = "msg_create_permanent_locked_account"

var _ sdk.Msg = &MsgCreateVestingAccount{}

var _ sdk.Msg = &MsgCreatePermanentLockedAccount{}

// NewMsgCreateVestingAccount returns a reference to a new MsgCreateVestingAccount.
//nolint:interfacer
func NewMsgCreateVestingAccount(fromAddr, toAddr sdk.AccAddress, amount sdk.Coins, endTime int64, delayed bool) *MsgCreateVestingAccount {
Expand Down Expand Up @@ -75,3 +80,51 @@ func (msg MsgCreateVestingAccount) GetSigners() []sdk.AccAddress {
}
return []sdk.AccAddress{from}
}

// NewMsgCreatePermanentLockedAccount returns a reference to a new MsgCreatePermanentLockedAccount.
//nolint:interfacer
func NewMsgCreatePermanentLockedAccount(fromAddr, toAddr sdk.AccAddress, amount sdk.Coins) *MsgCreatePermanentLockedAccount {
return &MsgCreatePermanentLockedAccount{
FromAddress: fromAddr.String(),
ToAddress: toAddr.String(),
Amount: amount,
}
}

// Route returns the message route for a MsgCreatePermanentLockedAccount.
func (msg MsgCreatePermanentLockedAccount) Route() string { return RouterKey }

// Type returns the message type for a MsgCreatePermanentLockedAccount.
func (msg MsgCreatePermanentLockedAccount) Type() string { return TypeMsgCreatePermanentLockedAccount }

// ValidateBasic Implements Msg.
func (msg MsgCreatePermanentLockedAccount) ValidateBasic() error {
if _, err := sdk.AccAddressFromBech32(msg.FromAddress); err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid sender address: %s", err)
}
if _, err := sdk.AccAddressFromBech32(msg.ToAddress); err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid recipient address: %s", err)
}

if !msg.Amount.IsValid() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, msg.Amount.String())
}

if !msg.Amount.IsAllPositive() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, msg.Amount.String())
}

return nil
}

// GetSignBytes returns the bytes all expected signers must sign over for a
// MsgCreatePermanentLockedAccount.
func (msg MsgCreatePermanentLockedAccount) GetSignBytes() []byte {
return sdk.MustSortJSON(amino.MustMarshalJSON(&msg))
}

// GetSigners returns the expected signers for a MsgCreatePermanentLockedAccount.
func (msg MsgCreatePermanentLockedAccount) GetSigners() []sdk.AccAddress {
from, _ := sdk.AccAddressFromBech32(msg.FromAddress)
return []sdk.AccAddress{from}
}
Loading

0 comments on commit 30db931

Please sign in to comment.