Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add MsgCreatePermanentLockedVestingAccount #42

Merged
merged 10 commits into from
Aug 17, 2021
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
66 changes: 66 additions & 0 deletions x/auth/vesting/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,69 @@ 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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be sdkerrors.ErrInvalidRequest,Wrapf.... which simplifies the code

}

var acc authtypes.AccountI
acc = 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
}
1 change: 1 addition & 0 deletions x/auth/vesting/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func RegisterInterfaces(registry types.InterfaceRegistry) {
registry.RegisterImplementations(
(*sdk.Msg)(nil),
&MsgCreateVestingAccount{},
&MsgCreatePermanentLockedAccount{},
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't end up having to add MsgCreatePermanentLockedAccount to the RegisterLegacyAminoCodec block up above (line 14). I'm not totally sure why... but I managed to test on my Ledger device with simapp, and I successfully used the CLI for creating & signing new permanent locked accounts.

)

msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
Expand Down
65 changes: 65 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,63 @@ 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 {
from, err := sdk.AccAddressFromBech32(msg.FromAddress)
if err != nil {
return err
}
to, err := sdk.AccAddressFromBech32(msg.ToAddress)
if err != nil {
return err
}
if err := sdk.VerifyAddressFormat(from); err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid sender address: %s", err)
}

if err := sdk.VerifyAddressFormat(to); err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid recipient address: %s", err)
}
clevinson marked this conversation as resolved.
Show resolved Hide resolved

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, err := sdk.AccAddressFromBech32(msg.FromAddress)
if err != nil {
panic(err)
}
clevinson marked this conversation as resolved.
Show resolved Hide resolved
return []sdk.AccAddress{from}
}
Loading