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(bank/v2): Add MsgSend handler #21736

Merged
merged 33 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
41f7632
add send msgs
hieuvubk Sep 15, 2024
1eb5c65
handler
hieuvubk Sep 15, 2024
845510c
send cmd
hieuvubk Sep 15, 2024
37cc625
regist msg & query handler
hieuvubk Sep 15, 2024
f0187dc
Merge branch 'main' into hieu/bankv2/handlers
hieuvubk Sep 15, 2024
f50fc0d
Merge branch 'main' into hieu/bankv2/handlers
hieuvubk Sep 16, 2024
f1edd6e
feedback
hieuvubk Sep 16, 2024
1f005a4
Merge branch 'hieu/bankv2/handlers' of https://github.com/cosmos/cosm…
hieuvubk Sep 16, 2024
f04ba3d
tx signer
hieuvubk Sep 16, 2024
d0ff17c
bankv2 genesis with balances and supply
hieuvubk Sep 16, 2024
5e5fb05
update comments
hieuvubk Sep 16, 2024
9bf855d
init bankv2 in testnet for systemtest
hieuvubk Sep 16, 2024
36d8752
test valid case
hieuvubk Sep 16, 2024
f774b20
msg mint & query balance
hieuvubk Sep 17, 2024
cadfdbe
balance cmd
hieuvubk Sep 18, 2024
2167d51
type
hieuvubk Sep 18, 2024
3e9a53d
re add to grpcQueryDecoders
hieuvubk Sep 18, 2024
29c9a8f
update
hieuvubk Sep 18, 2024
3778b6f
Merge branch 'main' into hieu/bankv2/handlers
hieuvubk Sep 19, 2024
1645d00
format response
hieuvubk Sep 19, 2024
44c3faa
systemtest
hieuvubk Sep 19, 2024
76e89cc
Merge branch 'hieu/bankv2/handlers' of https://github.com/cosmos/cosm…
hieuvubk Sep 19, 2024
3aafcb0
Merge branch 'main' into hieu/bankv2/handlers
hieuvubk Sep 19, 2024
0b5d9f3
lint
hieuvubk Sep 19, 2024
8e8297a
update tests
hieuvubk Sep 23, 2024
1fa11c9
no need
hieuvubk Sep 23, 2024
829c03c
feedback
hieuvubk Sep 23, 2024
f9fe061
err check
hieuvubk Sep 23, 2024
60485a0
lintttt
hieuvubk Sep 23, 2024
d94ccad
Merge branch 'main' into hieu/bankv2/handlers
hieuvubk Sep 23, 2024
b82b96c
resolve conflict
hieuvubk Sep 23, 2024
6581309
Merge branch 'hieu/bankv2/handlers' of https://github.com/cosmos/cosm…
hieuvubk Sep 23, 2024
2e312e7
Merge branch 'main' into hieu/bankv2/handlers
hieuvubk Sep 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions runtime/v2/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,10 +459,8 @@
func (m *MM[T]) RegisterServices(app *App[T]) error {
for _, module := range m.modules {
// register msg + query
if services, ok := module.(hasServicesV1); ok {
if err := registerServices(services, app, protoregistry.GlobalFiles); err != nil {
return err
}
if err := registerServices(module, app, protoregistry.GlobalFiles); err != nil {
return err
}

// register migrations
Expand Down Expand Up @@ -594,7 +592,7 @@
return nil
}

func registerServices[T transaction.Tx](s hasServicesV1, app *App[T], registry *protoregistry.Files) error {
func registerServices[T transaction.Tx](s appmodulev2.AppModule, app *App[T], registry *protoregistry.Files) error {
c := &configurator{
grpcQueryDecoders: map[string]func() gogoproto.Message{},
stfQueryRouter: app.queryRouterBuilder,
Expand All @@ -603,8 +601,28 @@
err: nil,
}

if err := s.RegisterServices(c); err != nil {
return fmt.Errorf("unable to register services: %w", err)
if services, ok := s.(hasServicesV1); ok {
if err := services.RegisterServices(c); err != nil {
return fmt.Errorf("unable to register services: %w", err)
}
} else {
// If module not implement RegisterServices, register msg & query handler.
if module, ok := s.(appmodulev2.HasMsgHandlers); ok {
module.RegisterMsgHandlers(app.msgRouterBuilder)
}

if module, ok := s.(appmodulev2.HasQueryHandlers); ok {
module.RegisterQueryHandlers(app.queryRouterBuilder)
// TODO: query regist by RegisterQueryHandlers not in grpcQueryDecoders
if module, ok := s.(interface {
GetQueryDecoders() map[string]func() gogoproto.Message
}); ok {
decoderMap := module.GetQueryDecoders()
for path, decoder := range decoderMap {
app.GRPCMethodsToMessageMap[path] = decoder
Comment on lines +620 to +622

Check warning

Code scanning / CodeQL

Iteration over map Warning

Iteration over map may be a possible source of non-determinism
}
}
}
}

if c.err != nil {
Expand Down
21 changes: 21 additions & 0 deletions simapp/v2/simdv2/cmd/testnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"cosmossdk.io/server/v2/cometbft"
"cosmossdk.io/server/v2/store"
banktypes "cosmossdk.io/x/bank/types"
bankv2types "cosmossdk.io/x/bank/v2/types"
stakingtypes "cosmossdk.io/x/staking/types"

"github.com/cosmos/cosmos-sdk/client"
Expand Down Expand Up @@ -402,6 +403,13 @@ func initGenFiles[T transaction.Tx](
}
appGenState[banktypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(&bankGenState)

var bankV2GenState bankv2types.GenesisState
clientCtx.Codec.MustUnmarshalJSON(appGenState[bankv2types.ModuleName], &bankV2GenState)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why we need this step as you are assigning bankV2GenState a new value in next step?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes should remove it

Copy link
Member

@julienrbrt julienrbrt Sep 23, 2024

Choose a reason for hiding this comment

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

I like the fallback tbh for now, maybe we should check if len(bankV2GenState.Balances) > 0 and if not, we use bank v1 genesis?

if len(bankV2GenState.Balances) == 0 {
bankV2GenState = getBankV2GenesisFromV1(bankGenState)
}
appGenState[bankv2types.ModuleName] = clientCtx.Codec.MustMarshalJSON(&bankV2GenState)

appGenStateJSON, err := json.MarshalIndent(appGenState, "", " ")
if err != nil {
return err
Expand Down Expand Up @@ -504,3 +512,16 @@ func writeFile(name, dir string, contents []byte) error {

return os.WriteFile(file, contents, 0o600)
}

// getBankV2GenesisFromV1 clones bank/v1 state to bank/v2
// since we not migrate yet
// TODO: Remove
func getBankV2GenesisFromV1(v1GenesisState banktypes.GenesisState) bankv2types.GenesisState {
Copy link
Member

Choose a reason for hiding this comment

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

let's add a comment on the rationale of doing this (basically, to be deleted after genesis migration command addition)

var v2GenesisState bankv2types.GenesisState
for _, balance := range v1GenesisState.Balances {
v2Balance := bankv2types.Balance(balance)
v2GenesisState.Balances = append(v2GenesisState.Balances, v2Balance)
v2GenesisState.Supply = v2GenesisState.Supply.Add(balance.Coins...)
}
return v2GenesisState
}
59 changes: 59 additions & 0 deletions tests/systemtests/bankv2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//go:build system_test

package systemtests

import (
"fmt"
"testing"

"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
)

func TestBankV2SendTxCmd(t *testing.T) {
// Currently only run with app v2
if !isV2() {
t.Skip()
}
// scenario: test bank send command
// given a running chain

sut.ResetChain(t)
cli := NewCLIWrapper(t, sut, verbose)

// get validator address
valAddr := gjson.Get(cli.Keys("keys", "list"), "1.address").String()
require.NotEmpty(t, valAddr)

// add new key
receiverAddr := cli.AddKey("account1")
denom := "stake"
sut.StartChain(t)

// query validator balance and make sure it has enough balance
var transferAmount int64 = 1000
raw := cli.CustomQuery("q", "bankv2", "balance", valAddr, denom)
valBalance := gjson.Get(raw, "balance.amount").Int()

require.Greater(t, valBalance, transferAmount, "not enough balance found with validator")

bankSendCmdArgs := []string{"tx", "bankv2", "send", valAddr, receiverAddr, fmt.Sprintf("%d%s", transferAmount, denom)}

// test valid transaction
rsp := cli.Run(append(bankSendCmdArgs, "--fees=1stake")...)
txResult, found := cli.AwaitTxCommitted(rsp)
require.True(t, found)
RequireTxSuccess(t, txResult)
hieuvubk marked this conversation as resolved.
Show resolved Hide resolved

// Check balance after send
valRaw := cli.CustomQuery("q", "bankv2", "balance", valAddr, denom)
valBalanceAfer := gjson.Get(valRaw, "balance.amount").Int()

// TODO: Make DeductFee ante handler work with bank/v2
require.Equal(t, valBalanceAfer, valBalance - transferAmount)

receiverRaw := cli.CustomQuery("q", "bankv2", "balance", receiverAddr, denom)
receiverBalance := gjson.Get(receiverRaw, "balance.amount").Int()
require.Equal(t, receiverBalance, transferAmount)

}
32 changes: 32 additions & 0 deletions x/bank/proto/cosmos/bank/v2/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,43 @@ package cosmos.bank.v2;
import "gogoproto/gogo.proto";
import "cosmos/bank/v2/bank.proto";
import "amino/amino.proto";
import "cosmos/base/v1beta1/coin.proto";
import "cosmos_proto/cosmos.proto";

option go_package = "cosmossdk.io/x/bank/v2/types";

// GenesisState defines the bank/v2 module's genesis state.
message GenesisState {
// params defines all the parameters of the module.
Params params = 1 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];

// balances is an array containing the balances of all the accounts.
repeated Balance balances = 2 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];

// supply represents the total supply. If it is left empty, then supply will be calculated based on the provided
// balances. Otherwise, it will be used to validate that the sum of the balances equals this amount.
repeated cosmos.base.v1beta1.Coin supply = 3 [
(amino.encoding) = "legacy_coins",
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true
];
}

// Balance defines an account address and balance pair used in the bank module's
// genesis state.
message Balance {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

// address is the address of the balance holder.
string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];

// coins defines the different coins this balance holds.
repeated cosmos.base.v1beta1.Coin coins = 2 [
(amino.encoding) = "legacy_coins",
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true
];
}
20 changes: 20 additions & 0 deletions x/bank/proto/cosmos/bank/v2/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package cosmos.bank.v2;
import "gogoproto/gogo.proto";
import "amino/amino.proto";
import "cosmos/bank/v2/bank.proto";
import "cosmos/base/v1beta1/coin.proto";
import "cosmos_proto/cosmos.proto";

option go_package = "cosmossdk.io/x/bank/v2/types";

Expand All @@ -14,4 +16,22 @@ message QueryParamsRequest {}
message QueryParamsResponse {
// params provides the parameters of the bank module.
Params params = 1 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];
}

// QueryBalanceRequest is the request type for the Query/Balance RPC method.
message QueryBalanceRequest {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

// address is the address to query balances for.
string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];

// denom is the coin denom to query balances for.
string denom = 2;
}

// QueryBalanceResponse is the response type for the Query/Balance RPC method.
message QueryBalanceResponse {
// balance is the balance of the coin.
cosmos.base.v1beta1.Coin balance = 1;
}
41 changes: 40 additions & 1 deletion x/bank/proto/cosmos/bank/v2/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "cosmos/bank/v2/bank.proto";
import "cosmos_proto/cosmos.proto";
import "cosmos/msg/v1/msg.proto";
import "amino/amino.proto";
import "cosmos/base/v1beta1/coin.proto";

option go_package = "cosmossdk.io/x/bank/v2/types";

Expand All @@ -23,4 +24,42 @@ message MsgUpdateParams {
}

// MsgUpdateParamsResponse defines the response structure for executing a MsgUpdateParams message.
message MsgUpdateParamsResponse {}
message MsgUpdateParamsResponse {}

// MsgSend represents a message to send coins from one account to another.
message MsgSend {
option (cosmos.msg.v1.signer) = "from_address";
option (amino.name) = "cosmos-sdk/x/bank/v2/MsgSend";

string from_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string to_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
repeated cosmos.base.v1beta1.Coin amount = 3 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(amino.encoding) = "legacy_coins",
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}

// MsgSendResponse defines the response structure for executing a MsgSend message.
message MsgSendResponse {}

// MsgMint is the Msg/Mint request type.
message MsgMint {
option (cosmos.msg.v1.signer) = "authority";
option (amino.name) = "cosmos-sdk/x/bank/v2/MsgMint";

// authority is the address that controls the module (defaults to x/gov unless overwritten).
string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];

string to_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
repeated cosmos.base.v1beta1.Coin amount = 3 [
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true,
(amino.encoding) = "legacy_coins",
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}

// MsgMint defines the response structure for executing a MsgMint message.
message MsgMintResponse {}
79 changes: 79 additions & 0 deletions x/bank/v2/client/cli/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package cli

import (
"errors"

gogoproto "github.com/cosmos/gogoproto/proto"
"github.com/spf13/cobra"

"cosmossdk.io/x/bank/v2/types"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
sdk "github.com/cosmos/cosmos-sdk/types"
)

const (
FlagDenom = "denom"
)

// GetQueryCmd returns the parent command for all x/bank CLi query commands. The
// provided clientCtx should have, at a minimum, a verifier, Tendermint RPC client,
// and marshaler set.
func GetQueryCmd() *cobra.Command {
cmd := &cobra.Command{
Use: types.ModuleName,
Short: "Querying commands for the bank module",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}

cmd.AddCommand(
GetBalanceCmd(),
)

return cmd
}

func GetBalanceCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "balance [address] [denom]",
Short: "Query an account balance by address and denom",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}

denom := args[1]
if denom == "" {
return errors.New("empty denom")
}

addr, err := sdk.AccAddressFromBech32(args[0])
if err != nil {
return err
}

ctx := cmd.Context()

req := types.NewQueryBalanceRequest(addr.String(), denom)
out := new(types.QueryBalanceResponse)

err = clientCtx.Invoke(ctx, gogoproto.MessageName(&types.QueryBalanceRequest{}), req, out)
if err != nil {
return err
}

return clientCtx.PrintProto(out)
},
}

cmd.Flags().String(FlagDenom, "", "The specific balance denomination to query for")
flags.AddQueryFlagsToCmd(cmd)
flags.AddPaginationFlagsToCmd(cmd, "all balances")

return cmd
}
Loading
Loading