From 16ce427a46f8c3d13cb449d06a9bc222f9a11626 Mon Sep 17 00:00:00 2001 From: Jaeseung Lee <41176085+tkxkd0159@users.noreply.github.com> Date: Tue, 25 Jun 2024 21:51:31 +0900 Subject: [PATCH] test: add CLI, gRPC, MsgServer tests (#1405) * Add global test codec * Add TransferTxCmd test * Add the remaining tx cli tests * Add cli query test * Add fbridge auth test case * Add grpc-query test * Add msg server tests * Add type tests * Add CHANGELOG (cherry picked from commit 97219d5ee0ebca0d324561b1a782938a97ce5eae) # Conflicts: # CHANGELOG.md --- CHANGELOG.md | 23 ++ client/tendermint.go | 28 ++ init_node.sh | 0 testutil/cli/tm_mocks.go | 41 ++ types/module/testutil/codec.go | 78 ++++ x/fbridge/client/cli/query.go | 6 +- x/fbridge/client/cli/query_test.go | 579 ++++++++++++++++++++++++++++ x/fbridge/client/cli/tx.go | 6 +- x/fbridge/client/cli/tx_test.go | 376 ++++++++++++++++++ x/fbridge/keeper/abci_test.go | 21 + x/fbridge/keeper/auth.go | 2 +- x/fbridge/keeper/auth_test.go | 22 ++ x/fbridge/keeper/genesis_test.go | 40 ++ x/fbridge/keeper/grpc_query_test.go | 354 +++++++++++++++++ x/fbridge/keeper/msg_server_test.go | 399 +++++++++++++++++++ x/fbridge/types/fbridge_test.go | 110 ++++++ x/fbridge/types/keys_test.go | 32 ++ x/fbridge/types/params_test.go | 66 ++++ 18 files changed, 2178 insertions(+), 5 deletions(-) create mode 100644 client/tendermint.go mode change 100644 => 100755 init_node.sh create mode 100644 testutil/cli/tm_mocks.go create mode 100644 types/module/testutil/codec.go create mode 100644 x/fbridge/client/cli/query_test.go create mode 100644 x/fbridge/client/cli/tx_test.go create mode 100644 x/fbridge/keeper/abci_test.go create mode 100644 x/fbridge/keeper/genesis_test.go create mode 100644 x/fbridge/keeper/grpc_query_test.go create mode 100644 x/fbridge/keeper/msg_server_test.go create mode 100644 x/fbridge/types/fbridge_test.go create mode 100644 x/fbridge/types/keys_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index d55520dd49..3f06560f38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,29 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features ### Improvements +<<<<<<< HEAD +======= +* (docs) [\#1120](https://github.com/Finschia/finschia-sdk/pull/1120) Update links in x/foundation README.md +* (feat) [\#1121](https://github.com/Finschia/finschia-sdk/pull/1121) Add update-censorship cmd to x/foundation cli +* (server) [#1153](https://github.com/Finschia/finschia-sdk/pull/1153) remove grpc replace directive +* (crypto) [\#1163](https://github.com/Finschia/finschia-sdk/pull/1163) Update some secp256k1 logics with latest `dcrec` +* (x/crisis) [#1167](https://github.com/Finschia/finschia-sdk/pull/1167) Use `CacheContext()` in `AssertInvariants()` +* (chore) [\#1168](https://github.com/Finschia/finschia-sdk/pull/1168) Replace `ExactArgs(0)` with `NoArgs()` in `x/upgrade` module +* (server) [\#1175](https://github.com/Finschia/finschia-sdk/pull/1175) Use go embed for swagger +* (x/collection) [\#1287](https://github.com/Finschia/finschia-sdk/pull/1287) add nft id validation to MsgSendNFT +* (types) [\#1314](https://github.com/Finschia/finschia-sdk/pull/1314) replace IsEqual with Equal +* (x/fswap) [\#1363](https://github.com/Finschia/finschia-sdk/pull/1363) introduce new event for MakeSwapProposal +* (x/fbridge) [\#1366](https://github.com/Finschia/finschia-sdk/pull/1366) Set target denom as module parameters +* (x/fbridge) [\#1369](https://github.com/Finschia/finschia-sdk/pull/1369) Add the event of `SetBridgeStatus` +* (x/fswap) [\#1372](https://github.com/Finschia/finschia-sdk/pull/1372) support message based proposals +* (x/fswap) [\#1387](https://github.com/Finschia/finschia-sdk/pull/1387) add new Swap query to get a single swap +* (x/fswap) [\#1382](https://github.com/Finschia/finschia-sdk/pull/1382) add validation & unit tests in fswap module +* (x/fbridge) [\#1395](https://github.com/Finschia/finschia-sdk/pull/1395) Return error instead of panic for behaviors triggered by client +* (x/fswap) [\#1396](https://github.com/Finschia/finschia-sdk/pull/1396) refactor to use snake_case in proto +* (x/fswap) [\#1391](https://github.com/Finschia/finschia-sdk/pull/1391) add cli_test for fswap module +* (x/fbridge) [\#1405](https://github.com/Finschia/finschia-sdk/pull/1405) Add CLI, gRPC, MsgServer tests +* (x/fswap) [\#1415](https://github.com/Finschia/finschia-sdk/pull/1415) add more testcases for fswap module +>>>>>>> 97219d5ee (test: add CLI, gRPC, MsgServer tests (#1405)) ### Bug Fixes diff --git a/client/tendermint.go b/client/tendermint.go new file mode 100644 index 0000000000..5465c398d4 --- /dev/null +++ b/client/tendermint.go @@ -0,0 +1,28 @@ +package client + +import ( + "context" + + rpcclient "github.com/tendermint/tendermint/rpc/client" + coretypes "github.com/tendermint/tendermint/rpc/core/types" +) + +// TendermintRPC defines the interface of a Tendermint RPC client needed for +// queries and transaction handling. +type TendermintRPC interface { + rpcclient.ABCIClient + + Validators(ctx context.Context, height *int64, page, perPage *int) (*coretypes.ResultValidators, error) + Status(context.Context) (*coretypes.ResultStatus, error) + Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error) + BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) + Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error) + Tx(ctx context.Context, hash []byte, prove bool) (*coretypes.ResultTx, error) + TxSearch( + ctx context.Context, + query string, + prove bool, + page, perPage *int, + orderBy string, + ) (*coretypes.ResultTxSearch, error) +} diff --git a/init_node.sh b/init_node.sh old mode 100644 new mode 100755 diff --git a/testutil/cli/tm_mocks.go b/testutil/cli/tm_mocks.go new file mode 100644 index 0000000000..0237fe1d88 --- /dev/null +++ b/testutil/cli/tm_mocks.go @@ -0,0 +1,41 @@ +package cli + +import ( + "context" + + abci "github.com/tendermint/tendermint/abci/types" + tmbytes "github.com/tendermint/tendermint/libs/bytes" + rpcclient "github.com/tendermint/tendermint/rpc/client" + rpcclientmock "github.com/tendermint/tendermint/rpc/client/mock" + coretypes "github.com/tendermint/tendermint/rpc/core/types" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/Finschia/finschia-sdk/client" +) + +var _ client.TendermintRPC = (*MockTendermintRPC)(nil) + +type MockTendermintRPC struct { + rpcclientmock.Client + + responseQuery abci.ResponseQuery +} + +// NewMockTendermintRPC returns a mock TendermintRPC implementation. +// It is used for CLI testing. +func NewMockTendermintRPC(respQuery abci.ResponseQuery) MockTendermintRPC { + return MockTendermintRPC{responseQuery: respQuery} +} + +func (MockTendermintRPC) BroadcastTxSync(context.Context, tmtypes.Tx) (*coretypes.ResultBroadcastTx, error) { + return &coretypes.ResultBroadcastTx{Code: 0}, nil +} + +func (m MockTendermintRPC) ABCIQueryWithOptions( + _ context.Context, + _ string, + _ tmbytes.HexBytes, + _ rpcclient.ABCIQueryOptions, +) (*coretypes.ResultABCIQuery, error) { + return &coretypes.ResultABCIQuery{Response: m.responseQuery}, nil +} diff --git a/types/module/testutil/codec.go b/types/module/testutil/codec.go new file mode 100644 index 0000000000..ce14c5baee --- /dev/null +++ b/types/module/testutil/codec.go @@ -0,0 +1,78 @@ +package testutil + +import ( + "github.com/Finschia/finschia-sdk/client" + "github.com/Finschia/finschia-sdk/codec" + "github.com/Finschia/finschia-sdk/codec/types" + "github.com/Finschia/finschia-sdk/std" + "github.com/Finschia/finschia-sdk/types/module" + "github.com/Finschia/finschia-sdk/x/auth/tx" +) + +// TestEncodingConfig defines an encoding configuration that is used for testing +// purposes. Note, MakeTestEncodingConfig takes a series of AppModuleBasic types +// which should only contain the relevant module being tested and any potential +// dependencies. +type TestEncodingConfig struct { + InterfaceRegistry types.InterfaceRegistry + Codec codec.Codec + TxConfig client.TxConfig + Amino *codec.LegacyAmino +} + +func MakeTestEncodingConfig(modules ...module.AppModuleBasic) TestEncodingConfig { + aminoCdc := codec.NewLegacyAmino() + interfaceRegistry := types.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + encCfg := TestEncodingConfig{ + InterfaceRegistry: interfaceRegistry, + Codec: cdc, + TxConfig: tx.NewTxConfig(cdc, tx.DefaultSignModes), + Amino: aminoCdc, + } + + mb := module.NewBasicManager(modules...) + + std.RegisterLegacyAminoCodec(encCfg.Amino) + std.RegisterInterfaces(encCfg.InterfaceRegistry) + mb.RegisterLegacyAminoCodec(encCfg.Amino) + mb.RegisterInterfaces(encCfg.InterfaceRegistry) + + return encCfg +} + +func MakeTestTxConfig() client.TxConfig { + interfaceRegistry := types.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + return tx.NewTxConfig(cdc, tx.DefaultSignModes) +} + +type TestBuilderTxConfig struct { + client.TxConfig + TxBuilder *TestTxBuilder +} + +func MakeBuilderTestTxConfig() TestBuilderTxConfig { + return TestBuilderTxConfig{ + TxConfig: MakeTestTxConfig(), + } +} + +func (cfg TestBuilderTxConfig) NewTxBuilder() client.TxBuilder { + if cfg.TxBuilder == nil { + cfg.TxBuilder = &TestTxBuilder{ + TxBuilder: cfg.TxConfig.NewTxBuilder(), + } + } + return cfg.TxBuilder +} + +type TestTxBuilder struct { + client.TxBuilder + ExtOptions []*types.Any +} + +func (b *TestTxBuilder) SetExtensionOptions(extOpts ...*types.Any) { + b.ExtOptions = extOpts +} diff --git a/x/fbridge/client/cli/query.go b/x/fbridge/client/cli/query.go index 19708afb6e..8f74b42073 100644 --- a/x/fbridge/client/cli/query.go +++ b/x/fbridge/client/cli/query.go @@ -13,7 +13,7 @@ import ( ) const ( - flagSequences = "sequences" + FlagSequences = "sequences" ) // NewQueryCmd returns the query commands for fbridge module @@ -100,7 +100,7 @@ func NewQuerySeqToBlocknumsCmd() *cobra.Command { } qc := types.NewQueryClient(clientCtx) - seqSlice, err := cmd.Flags().GetInt64Slice(flagSequences) + seqSlice, err := cmd.Flags().GetInt64Slice(FlagSequences) if err != nil { return err } @@ -119,7 +119,7 @@ func NewQuerySeqToBlocknumsCmd() *cobra.Command { }, } - cmd.Flags().Int64Slice(flagSequences, []int64{}, "comma separated list of bridge sequnece numbers") + cmd.Flags().Int64Slice(FlagSequences, []int64{}, "comma separated list of bridge sequnece numbers") flags.AddQueryFlagsToCmd(cmd) return cmd } diff --git a/x/fbridge/client/cli/query_test.go b/x/fbridge/client/cli/query_test.go new file mode 100644 index 0000000000..1527fa9d1b --- /dev/null +++ b/x/fbridge/client/cli/query_test.go @@ -0,0 +1,579 @@ +package cli_test + +import ( + "bytes" + "fmt" + "io" + + "github.com/gogo/protobuf/proto" + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/Finschia/finschia-sdk/client" + "github.com/Finschia/finschia-sdk/client/flags" + clitestutil "github.com/Finschia/finschia-sdk/testutil/cli" + "github.com/Finschia/finschia-sdk/x/fbridge/client/cli" + "github.com/Finschia/finschia-sdk/x/fbridge/types" +) + +const FlagOutput = "output" + +func (s *CLITestSuite) TestNewQueryCmd() { + cmdQuery := []string{ + "member", + "members", + "params", + "proposal", + "proposals", + "sending-next-seq", + "seq-to-blocknums", + "status", + "vote", + "votes", + } + + cmd := cli.NewQueryCmd() + for i, c := range cmd.Commands() { + s.Require().Equal(cmdQuery[i], c.Name()) + } +} + +func (s *CLITestSuite) TestQueryParams() { + cmd := cli.NewQueryParamsCmd() + s.Require().NotNil(cmd) + cmd.SetOut(io.Discard) + + tcs := []struct { + name string + ctxGen func() client.Context + args []string + expectResult proto.Message + }{ + { + "json output", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QueryParamsResponse{ + Params: types.DefaultParams(), + }) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{fmt.Sprintf("--%s=json", FlagOutput)}, + &types.QueryParamsResponse{}, + }, + } + + for _, tc := range tcs { + s.Run(tc.name, func() { + var outBuf bytes.Buffer + clientCtx := tc.ctxGen().WithOutput(&outBuf) + cmd.SetArgs(tc.args) + res, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + s.Require().NoError(err) + s.Require().NoError(s.encCfg.Codec.UnmarshalJSON(res.Bytes(), tc.expectResult)) + }) + } +} + +func (s *CLITestSuite) TestQueryNextSeqSend() { + cmd := cli.NewQueryNextSeqSendCmd() + s.Require().NotNil(cmd) + cmd.SetOut(io.Discard) + + tcs := []struct { + name string + ctxGen func() client.Context + args []string + expectResult proto.Message + }{ + { + "json output", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QueryNextSeqSendResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{fmt.Sprintf("--%s=json", FlagOutput)}, + &types.QueryNextSeqSendResponse{}, + }, + } + + for _, tc := range tcs { + s.Run(tc.name, func() { + cmd.SetArgs(tc.args) + res, err := clitestutil.ExecTestCLICmd(tc.ctxGen(), cmd, tc.args) + s.Require().NoError(err) + s.Require().NoError(s.encCfg.Codec.UnmarshalJSON(res.Bytes(), tc.expectResult)) + }) + } +} + +func (s *CLITestSuite) TestQuerySeqToBlocknumsCmd() { + cmd := cli.NewQuerySeqToBlocknumsCmd() + s.Require().NotNil(cmd) + cmd.SetOut(io.Discard) + + tcs := []struct { + name string + ctxGen func() client.Context + args []string + expectResult proto.Message + expectErr bool + }{ + { + "json output", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QuerySeqToBlocknumsResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{ + fmt.Sprintf("--%s=1", cli.FlagSequences), + fmt.Sprintf("--%s=json", FlagOutput), + }, + &types.QuerySeqToBlocknumsResponse{}, + false, + }, + { + "invalid seq", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QuerySeqToBlocknumsResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{fmt.Sprintf("--%s=1.3", cli.FlagSequences), fmt.Sprintf("--%s=json", FlagOutput)}, + &types.QuerySeqToBlocknumsResponse{}, + true, + }, + } + + for _, tc := range tcs { + s.Run(tc.name, func() { + cmd.SetArgs(tc.args) + res, err := clitestutil.ExecTestCLICmd(tc.ctxGen(), cmd, tc.args) + if tc.expectErr { + s.Require().Error(err) + s.Require().Error(s.encCfg.Codec.UnmarshalJSON(res.Bytes(), tc.expectResult)) + } else { + s.Require().NoError(err) + s.Require().NoError(s.encCfg.Codec.UnmarshalJSON(res.Bytes(), tc.expectResult)) + } + }) + } +} + +func (s *CLITestSuite) TestQueryMembersCmd() { + cmd := cli.NewQueryMembersCmd() + s.Require().NotNil(cmd) + cmd.SetOut(io.Discard) + + tcs := []struct { + name string + ctxGen func() client.Context + args []string + expectResult proto.Message + }{ + { + "json output", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QueryMembersResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{"guardian", fmt.Sprintf("--%s=json", FlagOutput)}, + &types.QueryMembersResponse{}, + }, + } + + for _, tc := range tcs { + s.Run(tc.name, func() { + cmd.SetArgs(tc.args) + res, err := clitestutil.ExecTestCLICmd(tc.ctxGen(), cmd, tc.args) + s.Require().NoError(err) + s.Require().NoError(s.encCfg.Codec.UnmarshalJSON(res.Bytes(), tc.expectResult)) + }) + } +} + +func (s *CLITestSuite) TestQueryMemberCmd() { + cmd := cli.NewQueryMemberCmd() + s.Require().NotNil(cmd) + cmd.SetOut(io.Discard) + + tcs := []struct { + name string + ctxGen func() client.Context + args []string + expectResult proto.Message + }{ + { + "json output", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QueryMemberResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{ + s.addrs[0].String(), + fmt.Sprintf("--%s=json", FlagOutput), + }, + &types.QueryMemberResponse{}, + }, + } + + for _, tc := range tcs { + s.Run(tc.name, func() { + cmd.SetArgs(tc.args) + res, err := clitestutil.ExecTestCLICmd(tc.ctxGen(), cmd, tc.args) + s.Require().NoError(err) + s.Require().NoError(s.encCfg.Codec.UnmarshalJSON(res.Bytes(), tc.expectResult)) + }) + } +} + +func (s *CLITestSuite) TestNewQueryProposalsCmd() { + cmd := cli.NewQueryProposalsCmd() + s.Require().NotNil(cmd) + cmd.SetOut(io.Discard) + + tcs := []struct { + name string + ctxGen func() client.Context + args []string + expectResult proto.Message + }{ + { + "json output", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QueryProposalsResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{fmt.Sprintf("--%s=json", FlagOutput)}, + &types.QueryProposalsResponse{}, + }, + { + "pagination", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QueryProposalsResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{ + fmt.Sprintf("--%s=100", flags.FlagLimit), + fmt.Sprintf("--%s=20", flags.FlagOffset), + fmt.Sprintf("--%s=true", flags.FlagCountTotal), + fmt.Sprintf("--%s=false", flags.FlagReverse), + fmt.Sprintf("--%s=json", FlagOutput), + }, + &types.QueryProposalsResponse{}, + }, + } + + for _, tc := range tcs { + s.Run(tc.name, func() { + cmd.SetArgs(tc.args) + res, err := clitestutil.ExecTestCLICmd(tc.ctxGen(), cmd, tc.args) + s.Require().NoError(err) + s.Require().NoError(s.encCfg.Codec.UnmarshalJSON(res.Bytes(), tc.expectResult)) + }) + } +} + +func (s *CLITestSuite) TestNewQueryProposalCmd() { + cmd := cli.NewQueryProposalCmd() + s.Require().NotNil(cmd) + cmd.SetOut(io.Discard) + + tcs := []struct { + name string + ctxGen func() client.Context + args []string + expectResult proto.Message + expectErr bool + }{ + { + "json output", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QueryProposalResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{ + "1", + fmt.Sprintf("--%s=json", FlagOutput), + }, + &types.QueryProposalResponse{}, + false, + }, + { + "no proposal ID", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QueryProposalResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{ + fmt.Sprintf("--%s=json", FlagOutput), + }, + &types.QueryProposalResponse{}, + true, + }, + { + "wrong proposal ID", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QueryProposalResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{ + "one", + fmt.Sprintf("--%s=json", FlagOutput), + }, + &types.QueryProposalResponse{}, + true, + }, + } + + for _, tc := range tcs { + s.Run(tc.name, func() { + cmd.SetArgs(tc.args) + res, err := clitestutil.ExecTestCLICmd(tc.ctxGen(), cmd, tc.args) + if tc.expectErr { + s.Require().Error(err) + s.Require().Error(s.encCfg.Codec.UnmarshalJSON(res.Bytes(), tc.expectResult)) + } else { + s.Require().NoError(err) + s.Require().NoError(s.encCfg.Codec.UnmarshalJSON(res.Bytes(), tc.expectResult)) + } + }) + } +} + +func (s *CLITestSuite) TestNewQueryVotesCmd() { + cmd := cli.NewQueryVotesCmd() + s.Require().NotNil(cmd) + cmd.SetOut(io.Discard) + + tcs := []struct { + name string + ctxGen func() client.Context + args []string + expectResult proto.Message + expectErr bool + }{ + { + "json output", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QueryVotesResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{ + "1", + fmt.Sprintf("--%s=json", FlagOutput), + }, + &types.QueryVotesResponse{}, + false, + }, + { + "no proposal ID", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QueryVotesResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{ + fmt.Sprintf("--%s=json", FlagOutput), + }, + &types.QueryVotesResponse{}, + true, + }, + { + "wrong proposal ID", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QueryVotesResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{ + "one", + fmt.Sprintf("--%s=json", FlagOutput), + }, + &types.QueryVotesResponse{}, + true, + }, + } + + for _, tc := range tcs { + s.Run(tc.name, func() { + cmd.SetArgs(tc.args) + res, err := clitestutil.ExecTestCLICmd(tc.ctxGen(), cmd, tc.args) + if tc.expectErr { + s.Require().Error(err) + s.Require().Error(s.encCfg.Codec.UnmarshalJSON(res.Bytes(), tc.expectResult)) + } else { + s.Require().NoError(err) + s.Require().NoError(s.encCfg.Codec.UnmarshalJSON(res.Bytes(), tc.expectResult)) + } + }) + } +} + +func (s *CLITestSuite) TestNewQueryVoteCmd() { + cmd := cli.NewQueryVoteCmd() + s.Require().NotNil(cmd) + cmd.SetOut(io.Discard) + + tcs := []struct { + name string + ctxGen func() client.Context + args []string + expectResult proto.Message + expectErr bool + }{ + { + "json output", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QueryVoteResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{ + "1", + s.addrs[0].String(), + fmt.Sprintf("--%s=json", FlagOutput), + }, + &types.QueryVoteResponse{}, + false, + }, + { + "no voter", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QueryVoteResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{ + "1", + fmt.Sprintf("--%s=json", FlagOutput), + }, + &types.QueryVoteResponse{}, + true, + }, + { + "no proposal ID", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QueryVoteResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{ + fmt.Sprintf("--%s=json", FlagOutput), + }, + &types.QueryVoteResponse{}, + true, + }, + { + "wrong proposal ID", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QueryVoteResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{ + "one", + s.addrs[0].String(), + fmt.Sprintf("--%s=json", FlagOutput), + }, + &types.QueryVoteResponse{}, + true, + }, + } + + for _, tc := range tcs { + s.Run(tc.name, func() { + cmd.SetArgs(tc.args) + res, err := clitestutil.ExecTestCLICmd(tc.ctxGen(), cmd, tc.args) + if tc.expectErr { + s.Require().Error(err) + s.Require().Error(s.encCfg.Codec.UnmarshalJSON(res.Bytes(), tc.expectResult)) + } else { + s.Require().NoError(err) + s.Require().NoError(s.encCfg.Codec.UnmarshalJSON(res.Bytes(), tc.expectResult)) + } + }) + } +} + +func (s *CLITestSuite) TestQueryBridgeStatusCmd() { + cmd := cli.NewQueryBridgeStatusCmd() + s.Require().NotNil(cmd) + cmd.SetOut(io.Discard) + + tcs := []struct { + name string + ctxGen func() client.Context + args []string + expectResult proto.Message + }{ + { + "json output", + func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&types.QueryBridgeStatusResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + }, + []string{ + fmt.Sprintf("--%s=json", FlagOutput), + }, + &types.QueryBridgeStatusResponse{}, + }, + } + + for _, tc := range tcs { + s.Run(tc.name, func() { + cmd.SetArgs(tc.args) + res, err := clitestutil.ExecTestCLICmd(tc.ctxGen(), cmd, tc.args) + s.Require().NoError(err) + s.Require().NoError(s.encCfg.Codec.UnmarshalJSON(res.Bytes(), tc.expectResult)) + }) + } +} diff --git a/x/fbridge/client/cli/tx.go b/x/fbridge/client/cli/tx.go index 887e5bf586..4899594512 100644 --- a/x/fbridge/client/cli/tx.go +++ b/x/fbridge/client/cli/tx.go @@ -175,10 +175,14 @@ func NewSetBridgeStatusTxCmd() *cobra.Command { "halt": types.StatusInactive, "resume": types.StatusActive, } + bs, found := conv[args[0]] + if !found { + return sdkerrors.ErrInvalidRequest.Wrapf("invalid bridge status: %s", args[0]) + } msg := types.MsgSetBridgeStatus{ Guardian: from, - Status: conv[args[0]], + Status: bs, } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) diff --git a/x/fbridge/client/cli/tx_test.go b/x/fbridge/client/cli/tx_test.go new file mode 100644 index 0000000000..6da55324b1 --- /dev/null +++ b/x/fbridge/client/cli/tx_test.go @@ -0,0 +1,376 @@ +package cli_test + +import ( + "fmt" + "io" + "strings" + "testing" + + "github.com/gogo/protobuf/proto" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" + rpcclientmock "github.com/tendermint/tendermint/rpc/client/mock" + + "github.com/Finschia/finschia-sdk/client" + "github.com/Finschia/finschia-sdk/client/flags" + "github.com/Finschia/finschia-sdk/crypto/hd" + "github.com/Finschia/finschia-sdk/crypto/keyring" + "github.com/Finschia/finschia-sdk/tests/mocks" + clitestutil "github.com/Finschia/finschia-sdk/testutil/cli" + sdk "github.com/Finschia/finschia-sdk/types" + testutilmod "github.com/Finschia/finschia-sdk/types/module/testutil" + "github.com/Finschia/finschia-sdk/x/fbridge/client/cli" + fbridgem "github.com/Finschia/finschia-sdk/x/fbridge/module" +) + +type CLITestSuite struct { + suite.Suite + + kr keyring.Keyring + encCfg testutilmod.TestEncodingConfig + baseCtx client.Context + clientCtx client.Context + addrs []sdk.AccAddress +} + +func TestCLITestSuite(t *testing.T) { + suite.Run(t, new(CLITestSuite)) +} + +func (s *CLITestSuite) SetupSuite() { + ctrl := gomock.NewController(s.T()) + defer ctrl.Finish() + ar := mocks.NewMockAccountRetriever(ctrl) + + s.encCfg = testutilmod.MakeTestEncodingConfig(fbridgem.AppModule{}) + s.kr = keyring.NewInMemory() + + s.baseCtx = client.Context{}. + WithKeyring(s.kr). + WithTxConfig(s.encCfg.TxConfig). + WithCodec(s.encCfg.Codec). + WithInterfaceRegistry(s.encCfg.InterfaceRegistry). + WithLegacyAmino(s.encCfg.Amino). + WithClient(clitestutil.MockTendermintRPC{Client: rpcclientmock.Client{}}). + WithAccountRetriever(ar). + WithOutput(io.Discard). + WithChainID("test-chain") + + ctxGen := func() client.Context { + bz, _ := s.encCfg.Codec.Marshal(&sdk.TxResponse{}) + c := clitestutil.NewMockTendermintRPC(abci.ResponseQuery{ + Value: bz, + }) + return s.baseCtx.WithClient(c) + } + + s.clientCtx = ctxGen() + s.addrs = make([]sdk.AccAddress, 0) + for i := 0; i < 3; i++ { + k, _, err := s.clientCtx.Keyring.NewMnemonic(fmt.Sprintf("TestAccount-%d", i), keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) + s.Require().NoError(err) + pub := k.GetPubKey() + newAddr := sdk.AccAddress(pub.Address()) + s.addrs = append(s.addrs, newAddr) + + ar.EXPECT().EnsureExists(gomock.Any(), newAddr).Return(nil).AnyTimes() + ar.EXPECT().GetAccountNumberSequence(gomock.Any(), newAddr).Return(uint64(i), uint64(1), nil).AnyTimes() + } +} + +func cliArgs(args ...string) []string { + return append(args, []string{ + fmt.Sprintf("--%s=json", FlagOutput), + fmt.Sprintf("--%s=home", flags.FlagKeyringDir), + fmt.Sprintf("--%s=mynote", flags.FlagNote), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--%s=1.2", flags.FlagGasAdjustment), + fmt.Sprintf("--%s=false", flags.FlagUseLedger), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=false", flags.FlagDryRun), + fmt.Sprintf("--%s=false", flags.FlagGenerateOnly), + fmt.Sprintf("--%s=false", flags.FlagOffline), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=direct", flags.FlagSignMode), + fmt.Sprintf("--%s=%d", flags.FlagTimeoutHeight, 0), + }..., + ) +} + +func (s *CLITestSuite) TestNewTxCmd() { + cmdQuery := []string{ + "add-vote-for-role", + "set-bridge-status", + "suggest-role", + "transfer", + } + + cmd := cli.NewTxCmd() + for i, c := range cmd.Commands() { + s.Require().Equal(cmdQuery[i], c.Name()) + } +} + +func (s *CLITestSuite) TestNewTransferTxCmd() { + cmd := cli.NewTransferTxCmd() + s.Require().NotNil(cmd) + + tcs := []struct { + name string + args []string + expectErr bool + respType proto.Message + expectedCode uint32 + }{ + { + name: "valid request", + args: cliArgs( + s.addrs[1].String(), + "10stake", + fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0]), + ), + expectErr: false, + respType: &sdk.TxResponse{}, + expectedCode: 0, + }, + { + name: "invalid from address", + args: cliArgs( + s.addrs[0].String(), + "10stake", + fmt.Sprintf("--%s=%s", flags.FlagFrom, "link1..."), + ), + expectErr: true, + }, + { + name: "invalid decimal coin", + args: cliArgs( + s.addrs[1].String(), + fmt.Sprintf("10%s", strings.Repeat("a", 300)), + fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0]), + ), + expectErr: true, + }, + { + name: "more than one coin", + args: cliArgs( + s.addrs[1].String(), + "10stake,20cony", + fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0]), + ), + expectErr: true, + }, + } + + for _, tc := range tcs { + s.Run(tc.name, func() { + out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err, out.String()) + s.Require().NoError(s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + tsResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, tsResp.Code, out.String()) + } + }) + } +} + +func (s *CLITestSuite) TestNewSuggestRoleTxCmd() { + cmd := cli.NewSuggestRoleTxCmd() + s.Require().NotNil(cmd) + + tcs := []struct { + name string + args []string + expectErr bool + respType proto.Message + expectedCode uint32 + }{ + { + name: "invalid from address", + args: cliArgs( + s.addrs[1].String(), + "guardian", + fmt.Sprintf("--%s=%s", flags.FlagFrom, "link1..."), + ), + expectErr: true, + }, + { + name: "invalid role", + args: cliArgs( + s.addrs[1].String(), + "random", + fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0].String()), + ), + expectErr: true, + }, + { + name: "valid request", + args: cliArgs( + s.addrs[1].String(), + "guardian", + fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0].String()), + ), + expectErr: false, + respType: &sdk.TxResponse{}, + expectedCode: 0, + }, + } + + for _, tc := range tcs { + s.Run(tc.name, func() { + out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err, out.String()) + s.Require().NoError(s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + tsResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, tsResp.Code, out.String()) + } + }) + } +} + +func (s *CLITestSuite) TestNewAddVoteForRoleTxCmd() { + cmd := cli.NewAddVoteForRoleTxCmd() + s.Require().NotNil(cmd) + + tcs := []struct { + name string + args []string + expectErr bool + respType proto.Message + expectedCode uint32 + }{ + { + name: "invalid from address", + args: cliArgs( + "1", + "yes", + fmt.Sprintf("--%s=%s", flags.FlagFrom, "link1..."), + ), + expectErr: true, + }, + { + name: "invalid proposal ID", + args: cliArgs( + "0xf", + "yes", + fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0].String()), + ), + expectErr: true, + }, + { + name: "invalid vote option", + args: cliArgs( + "1", + "n/a", + fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0].String()), + ), + expectErr: true, + }, + { + name: "valid request - yes", + args: cliArgs( + "1", + "yes", + fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0].String()), + ), + expectErr: false, + respType: &sdk.TxResponse{}, + expectedCode: 0, + }, + { + name: "valid request - no", + args: cliArgs( + "1", + "no", + fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0].String()), + ), + expectErr: false, + respType: &sdk.TxResponse{}, + expectedCode: 0, + }, + } + + for _, tc := range tcs { + s.Run(tc.name, func() { + out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err, out.String()) + s.Require().NoError(s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + tsResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, tsResp.Code, out.String()) + } + }) + } +} + +func (s *CLITestSuite) TestNewSetBridgeStatusTxCmd() { + cmd := cli.NewSetBridgeStatusTxCmd() + s.Require().NotNil(cmd) + tcs := []struct { + name string + args []string + expectErr bool + respType proto.Message + expectedCode uint32 + }{ + { + name: "invalid from address", + args: cliArgs( + "halt", + fmt.Sprintf("--%s=%s", flags.FlagFrom, "link1..."), + ), + expectErr: true, + }, + { + name: "invalid brdige status", + args: cliArgs( + "wrongstatus", + fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0].String()), + ), + expectErr: true, + }, + { + name: "valid request - halt", + args: cliArgs( + "halt", + fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0].String()), + ), + expectErr: false, + respType: &sdk.TxResponse{}, + expectedCode: 0, + }, + { + name: "valid request - resume", + args: cliArgs( + "resume", + fmt.Sprintf("--%s=%s", flags.FlagFrom, s.addrs[0].String()), + ), + expectErr: false, + respType: &sdk.TxResponse{}, + expectedCode: 0, + }, + } + + for _, tc := range tcs { + s.Run(tc.name, func() { + out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err, out.String()) + s.Require().NoError(s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + tsResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, tsResp.Code, out.String()) + } + }) + } +} diff --git a/x/fbridge/keeper/abci_test.go b/x/fbridge/keeper/abci_test.go new file mode 100644 index 0000000000..0eab31ed2c --- /dev/null +++ b/x/fbridge/keeper/abci_test.go @@ -0,0 +1,21 @@ +package keeper_test + +import ( + "github.com/Finschia/finschia-sdk/simapp" + sdk "github.com/Finschia/finschia-sdk/types" + "github.com/Finschia/finschia-sdk/x/fbridge/types" +) + +func (s *IntegrationTestSuite) TestBeginBlocker() { + dummy := simapp.AddTestAddrs(s.app, s.ctx, 1, sdk.NewInt(1000000000))[0] + _, err := s.app.FbridgeKeeper.RegisterRoleProposal(s.ctx, s.guardians[0], dummy, types.RoleGuardian) + s.Require().NoError(err) + + bh := s.ctx.BlockHeader() + bh.Time = s.ctx.BlockHeader().Time.AddDate(0, 0, 1) + s.ctx = s.ctx.WithBlockHeader(bh) + s.app.FbridgeKeeper.BeginBlocker(s.ctx) + + _, err = s.app.FbridgeKeeper.RegisterRoleProposal(s.ctx, s.guardians[0], dummy, types.RoleGuardian) + s.Require().NoError(err) +} diff --git a/x/fbridge/keeper/auth.go b/x/fbridge/keeper/auth.go index 833e53399c..2f860fa87a 100644 --- a/x/fbridge/keeper/auth.go +++ b/x/fbridge/keeper/auth.go @@ -309,7 +309,7 @@ func (k Keeper) GetBridgeSwitch(ctx sdk.Context, guardian sdk.AccAddress) (types store := ctx.KVStore(k.storeKey) bz := store.Get(types.BridgeSwitchKey(guardian)) if bz == nil { - panic("bridge switch must be set at genesis") + panic("bridge switch should have been set when granting the guardian role") } return types.BridgeSwitch{Guardian: guardian.String(), Status: types.BridgeStatus(binary.BigEndian.Uint32(bz))}, nil diff --git a/x/fbridge/keeper/auth_test.go b/x/fbridge/keeper/auth_test.go index 0946037a7d..72b21a8833 100644 --- a/x/fbridge/keeper/auth_test.go +++ b/x/fbridge/keeper/auth_test.go @@ -16,6 +16,8 @@ func TestAssignRole(t *testing.T) { err := k.InitGenesis(ctx, types.DefaultGenesisState()) require.NoError(t, err) + const wrongProposalID = 10 + // 1. Bridge authority assigns an address to a guardian role p, err := k.RegisterRoleProposal(ctx, addrs[0], addrs[1], types.RoleGuardian) require.Error(t, err, "role proposal must not be passed without authority") @@ -34,11 +36,21 @@ func TestAssignRole(t *testing.T) { p, err = k.RegisterRoleProposal(ctx, addrs[0], addrs[1], types.RoleGuardian) require.NoError(t, err, "role proposal must be passed with guardian role") require.EqualValues(t, 2, p.Id) + + err = k.addVote(ctx, p.Id, addrs[2], types.OptionYes) + require.Error(t, err, "only guardian can execute this action") + err = k.addVote(ctx, wrongProposalID, addrs[0], types.OptionYes) + require.Error(t, err, "this proposal must not be found") + err = k.addVote(ctx, p.Id, addrs[0], types.OptionEmpty) + require.Error(t, err, "invalid vote option must be rejected") err = k.addVote(ctx, p.Id, addrs[0], types.OptionYes) require.NoError(t, err) + _, err = k.GetVote(ctx, wrongProposalID, addrs[0]) + require.Error(t, err, "this proposal must not be found") opt, err := k.GetVote(ctx, p.Id, addrs[0]) require.NoError(t, err) require.Equal(t, types.OptionYes, opt) + err = k.updateRole(ctx, types.RoleGuardian, addrs[1]) require.NoError(t, err) require.Equal(t, types.RoleMetadata{Guardian: 2, Operator: 0, Judge: 0}, k.GetRoleMetadata(ctx)) @@ -47,6 +59,8 @@ func TestAssignRole(t *testing.T) { for _, sw := range sws { require.Equal(t, types.StatusActive, sw.Status) } + _, err = k.GetBridgeSwitch(ctx, addrs[2]) + require.Error(t, err, "this address is not a guardian") // 3. Guardian assigns an address to an operator role err = k.updateRole(ctx, types.RoleOperator, addrs[1]) @@ -54,8 +68,16 @@ func TestAssignRole(t *testing.T) { require.Equal(t, types.RoleMetadata{Guardian: 1, Operator: 1, Judge: 0}, k.GetRoleMetadata(ctx)) // 4. Guardian assigns an address to a same role + _, err = k.RegisterRoleProposal(ctx, addrs[0], addrs[1], types.RoleOperator) + require.Error(t, err, "the role proposal cannot be submitted if target's role is equal to the role in proposal") err = k.updateRole(ctx, types.RoleOperator, addrs[1]) require.NoError(t, err) + + // 5.Disassociate an address from a role + err = k.updateRole(ctx, types.RoleJudge, addrs[1]) + require.NoError(t, err) + err = k.updateRole(ctx, types.RoleEmpty, addrs[1]) + require.NoError(t, err) } func TestBridgeHaltAndResume(t *testing.T) { diff --git a/x/fbridge/keeper/genesis_test.go b/x/fbridge/keeper/genesis_test.go new file mode 100644 index 0000000000..bee4bd4436 --- /dev/null +++ b/x/fbridge/keeper/genesis_test.go @@ -0,0 +1,40 @@ +package keeper_test + +import ( + sdk "github.com/Finschia/finschia-sdk/types" + "github.com/Finschia/finschia-sdk/x/fbridge/types" +) + +func (s *IntegrationTestSuite) TestExportImportGenesis() { + goctx := sdk.WrapSDKContext(s.ctx) + const expProposalID uint64 = 5 + + _, err := s.msgServer.Transfer(goctx, &types.MsgTransfer{ + Sender: s.guardians[0].String(), + Receiver: s.ethAddr, + Amount: sdk.NewInt(100), + }) + s.Require().NoError(err) + + _, err = s.msgServer.SuggestRole(goctx, &types.MsgSuggestRole{ + From: s.guardians[0].String(), + Target: s.operator.String(), + Role: types.RoleJudge, + }) + s.Require().NoError(err) + + _, err = s.msgServer.AddVoteForRole(goctx, &types.MsgAddVoteForRole{ + From: s.guardians[0].String(), + ProposalId: expProposalID, + Option: types.OptionYes, + }) + s.Require().NoError(err) + + gen := s.app.FbridgeKeeper.ExportGenesis(s.ctx) + gen.SendingState.SeqToBlocknum[0].Blocknum = 1 + err = types.ValidateGenesis(*gen) + s.Require().NoError(err) + + err = s.app.FbridgeKeeper.InitGenesis(s.ctx, gen) + s.Require().NoError(err) +} diff --git a/x/fbridge/keeper/grpc_query_test.go b/x/fbridge/keeper/grpc_query_test.go new file mode 100644 index 0000000000..3412f93468 --- /dev/null +++ b/x/fbridge/keeper/grpc_query_test.go @@ -0,0 +1,354 @@ +package keeper_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/Finschia/finschia-sdk/baseapp" + "github.com/Finschia/finschia-sdk/simapp" + sdk "github.com/Finschia/finschia-sdk/types" + "github.com/Finschia/finschia-sdk/types/query" + "github.com/Finschia/finschia-sdk/x/fbridge/keeper" + "github.com/Finschia/finschia-sdk/x/fbridge/types" +) + +func TestIntegrationTestSuite(t *testing.T) { + suite.Run(t, new(IntegrationTestSuite)) +} + +type IntegrationTestSuite struct { + suite.Suite + + app *simapp.SimApp + ctx sdk.Context + queryClient types.QueryClient + msgServer types.MsgServer + guardians []sdk.AccAddress + operator sdk.AccAddress + ethAddr string +} + +func (s *IntegrationTestSuite) SetupTest() { + s.app = simapp.Setup(false) + s.ctx = s.app.BaseApp.NewContext(false, tmproto.Header{}) + + queryHelper := baseapp.NewQueryServerTestHelper(s.ctx, s.app.InterfaceRegistry()) + s.queryClient = types.NewQueryClient(queryHelper) + types.RegisterQueryServer(queryHelper, s.app.FbridgeKeeper) + s.msgServer = keeper.NewMsgServer(s.app.FbridgeKeeper) + + s.guardians = simapp.AddTestAddrs(s.app, s.ctx, 3, sdk.NewInt(1000000000)) + for _, guardian := range s.guardians { + _, err := s.app.FbridgeKeeper.RegisterRoleProposal(s.ctx, types.DefaultAuthority(), guardian, types.RoleGuardian) + s.Require().NoError(err) + } + s.operator = simapp.AddTestAddrs(s.app, s.ctx, 1, sdk.NewInt(1000000000))[0] + _, err := s.app.FbridgeKeeper.RegisterRoleProposal(s.ctx, types.DefaultAuthority(), s.operator, types.RoleOperator) + s.Require().NoError(err) + s.app.FbridgeKeeper.EndBlocker(s.ctx) + + s.ethAddr = "0x1A7C26B0437Aa2d3c8454383650a5D3c35087f91" +} + +func (s *IntegrationTestSuite) TestInactiveQuries() { + goctx := sdk.WrapSDKContext(s.ctx) + + s.Require().Panics(func() { + _, _ = s.queryClient.GreatestSeqByOperator(goctx, &types.QueryGreatestSeqByOperatorRequest{}) + }) + + s.Require().Panics(func() { + _, _ = s.queryClient.GreatestConsecutiveConfirmedSeq(goctx, &types.QueryGreatestConsecutiveConfirmedSeqRequest{}) + }) + + s.Require().Panics(func() { + _, _ = s.queryClient.SubmittedProvision(goctx, &types.QuerySubmittedProvisionRequest{}) + }) + + s.Require().Panics(func() { + _, _ = s.queryClient.ConfirmedProvision(goctx, &types.QueryConfirmedProvisionRequest{}) + }) + + s.Require().Panics(func() { + _, _ = s.queryClient.NeededSubmissionSeqs(goctx, &types.QueryNeededSubmissionSeqsRequest{}) + }) + + s.Require().Panics(func() { + _, _ = s.queryClient.Commitments(goctx, &types.QueryCommitmentsRequest{}) + }) +} + +func (s *IntegrationTestSuite) TestParams() { + goctx := sdk.WrapSDKContext(s.ctx) + res, err := s.queryClient.Params(goctx, &types.QueryParamsRequest{}) + s.Require().NoError(err) + s.Require().EqualValues(types.DefaultParams(), res.Params) +} + +func (s *IntegrationTestSuite) TestNextSeqSend() { + goctx := sdk.WrapSDKContext(s.ctx) + res, err := s.queryClient.NextSeqSend(goctx, &types.QueryNextSeqSendRequest{}) + s.Require().NoError(err) + s.Require().EqualValues(1, res.Seq) +} + +func (s *IntegrationTestSuite) TestSeqToBlocknums() { + goctx := sdk.WrapSDKContext(s.ctx) + req := new(types.QuerySeqToBlocknumsRequest) + + tcs := map[string]struct { + expErr bool + expBlock []uint64 + malleate func() + }{ + "empty request": { + expErr: true, + malleate: func() { + req = &types.QuerySeqToBlocknumsRequest{} + }, + }, + "exceed upper bound (1000)": { + expErr: true, + malleate: func() { + seqs := [1001]uint64{} + req = &types.QuerySeqToBlocknumsRequest{Seqs: seqs[:]} + }, + }, + "seq not found": { + expErr: true, + malleate: func() { + req = &types.QuerySeqToBlocknumsRequest{Seqs: []uint64{1001}} + }, + }, + "success": { + expErr: false, + expBlock: []uint64{0, 0}, + malleate: func() { + _, err := s.msgServer.Transfer(goctx, &types.MsgTransfer{ + Sender: s.guardians[0].String(), + Receiver: s.ethAddr, + Amount: sdk.NewInt(100), + }) + s.Require().NoError(err) + req = &types.QuerySeqToBlocknumsRequest{Seqs: []uint64{1, 2}} + _, err = s.msgServer.Transfer(goctx, &types.MsgTransfer{ + Sender: s.guardians[1].String(), + Receiver: s.ethAddr, + Amount: sdk.NewInt(100), + }) + s.Require().NoError(err) + + req = &types.QuerySeqToBlocknumsRequest{Seqs: []uint64{1, 2}} + }, + }, + } + + for name, tc := range tcs { + s.Run(name, func() { + tc.malleate() + res, err := s.queryClient.SeqToBlocknums(goctx, req) + if tc.expErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().Equal(tc.expBlock, res.Blocknums) + } + }) + } +} + +func (s *IntegrationTestSuite) TestMembers() { + goctx := sdk.WrapSDKContext(s.ctx) + req := new(types.QueryMembersRequest) + tcs := map[string]struct { + expErr bool + expLen int + malleate func() + }{ + "query all members": { + expErr: false, + expLen: 4, + malleate: func() { + req = &types.QueryMembersRequest{} + }, + }, + "query guardian group": { + expErr: false, + expLen: 3, + malleate: func() { + req = &types.QueryMembersRequest{Role: "guardian"} + }, + }, + "query operator group": { + expErr: false, + expLen: 1, + malleate: func() { + req = &types.QueryMembersRequest{Role: "operator"} + }, + }, + "query judge group": { + expErr: false, + expLen: 0, + malleate: func() { + req = &types.QueryMembersRequest{Role: "judge"} + }, + }, + "query invalid group": { + expErr: true, + malleate: func() { + req = &types.QueryMembersRequest{Role: "invalid"} + }, + }, + } + + for name, tc := range tcs { + s.Run(name, func() { + tc.malleate() + res, err := s.queryClient.Members(goctx, req) + if tc.expErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().Len(res.Members, tc.expLen) + } + }) + } +} + +func (s *IntegrationTestSuite) TestMember() { + goctx := sdk.WrapSDKContext(s.ctx) + req := new(types.QueryMemberRequest) + tcs := map[string]struct { + expErr bool + expRole string + malleate func() + }{ + "query a member who has a role": { + expErr: false, + expRole: "GUARDIAN", + malleate: func() { + req = &types.QueryMemberRequest{Address: s.guardians[0].String()} + }, + }, + "query a member who doesn't have a role": { + expErr: true, + malleate: func() { + dummy := simapp.AddTestAddrs(s.app, s.ctx, 1, sdk.NewInt(1000000000))[0] + req = &types.QueryMemberRequest{Address: dummy.String()} + }, + }, + } + + for name, tc := range tcs { + s.Run(name, func() { + tc.malleate() + if tc.expErr { + _, err := s.queryClient.Member(goctx, req) + s.Require().Error(err) + } else { + res, err := s.queryClient.Member(goctx, req) + s.Require().NoError(err) + s.Require().Equal(tc.expRole, res.Role) + } + }) + } +} + +func (s *IntegrationTestSuite) TestProposals() { + goctx := sdk.WrapSDKContext(s.ctx) + expProposalID := []uint64{5, 6} + _, err := s.msgServer.SuggestRole(goctx, &types.MsgSuggestRole{ + From: s.guardians[0].String(), + Target: s.guardians[1].String(), + Role: types.RoleJudge, + }) + s.Require().NoError(err) + _, err = s.msgServer.SuggestRole(goctx, &types.MsgSuggestRole{ + From: s.guardians[0].String(), + Target: s.guardians[2].String(), + Role: types.RoleOperator, + }) + s.Require().NoError(err) + + req := &types.QueryProposalsRequest{ + Pagination: &query.PageRequest{ + Offset: 0, + Limit: 10, + CountTotal: true, + Reverse: false, + }, + } + + res, err := s.queryClient.Proposals(goctx, req) + s.Require().NoError(err) + for i, proposal := range res.Proposals { + s.Require().Equal(expProposalID[i], proposal.Id) + } + + req2 := &types.QueryProposalRequest{ + ProposalId: expProposalID[1], + } + + res2, err := s.queryClient.Proposal(goctx, req2) + s.Require().NoError(err) + s.Require().Equal(expProposalID[1], res2.Proposal.Id) + + req2.ProposalId++ + _, err = s.queryClient.Proposal(goctx, req2) + s.Require().Error(err) +} + +func (s *IntegrationTestSuite) TestVotes() { + goctx := sdk.WrapSDKContext(s.ctx) + const expProposalID uint64 = 5 + _, err := s.msgServer.SuggestRole(goctx, &types.MsgSuggestRole{ + From: s.guardians[0].String(), + Target: s.guardians[1].String(), + Role: types.RoleJudge, + }) + s.Require().NoError(err) + _, err = s.msgServer.AddVoteForRole(goctx, &types.MsgAddVoteForRole{ + From: s.guardians[0].String(), + ProposalId: expProposalID, + Option: types.OptionYes, + }) + s.Require().NoError(err) + + req := &types.QueryVotesRequest{ + ProposalId: expProposalID, + } + + res, err := s.queryClient.Votes(goctx, req) + s.Require().NoError(err) + s.Require().Equal(expProposalID, res.Votes[0].ProposalId) + s.Require().Equal(s.guardians[0].String(), res.Votes[0].Voter) + s.Require().Equal(types.OptionYes, res.Votes[0].Option) + + req.ProposalId++ + res, err = s.queryClient.Votes(goctx, req) + s.Require().NoError(err) + s.Require().Empty(res.Votes) + + req2 := &types.QueryVoteRequest{ + ProposalId: expProposalID, + Voter: s.guardians[0].String(), + } + res2, err := s.queryClient.Vote(goctx, req2) + s.Require().NoError(err) + s.Require().Equal(expProposalID, res2.Vote.ProposalId) + s.Require().Equal(s.guardians[0].String(), res2.Vote.Voter) + s.Require().Equal(types.OptionYes, res2.Vote.Option) + + req2.ProposalId++ + _, err = s.queryClient.Vote(goctx, req2) + s.Require().Error(err) +} + +func (s *IntegrationTestSuite) TestBridgeStatus() { + goctx := sdk.WrapSDKContext(s.ctx) + res, err := s.queryClient.BridgeStatus(goctx, &types.QueryBridgeStatusRequest{}) + s.Require().NoError(err) + s.Require().EqualValues(types.StatusActive, res.Status) +} diff --git a/x/fbridge/keeper/msg_server_test.go b/x/fbridge/keeper/msg_server_test.go new file mode 100644 index 0000000000..64fbb80974 --- /dev/null +++ b/x/fbridge/keeper/msg_server_test.go @@ -0,0 +1,399 @@ +package keeper_test + +import ( + "context" + "fmt" + + sdk "github.com/Finschia/finschia-sdk/types" + "github.com/Finschia/finschia-sdk/x/fbridge/types" +) + +func (s *IntegrationTestSuite) TestInactiveTxs() { + goctx := sdk.WrapSDKContext(s.ctx) + + s.Require().Panics(func() { + _, _ = s.msgServer.Provision(goctx, &types.MsgProvision{}) + }) + + s.Require().Panics(func() { + _, _ = s.msgServer.HoldTransfer(goctx, &types.MsgHoldTransfer{}) + }) + + s.Require().Panics(func() { + _, _ = s.msgServer.ReleaseTransfer(goctx, &types.MsgReleaseTransfer{}) + }) + + s.Require().Panics(func() { + _, _ = s.msgServer.RemoveProvision(goctx, &types.MsgRemoveProvision{}) + }) + + s.Require().Panics(func() { + _, _ = s.msgServer.ClaimBatch(goctx, &types.MsgClaimBatch{}) + }) + + s.Require().Panics(func() { + _, _ = s.msgServer.Claim(goctx, &types.MsgClaim{}) + }) +} + +func (s *IntegrationTestSuite) TestUpdateParams() { + tcs := map[string]struct { + msg types.MsgUpdateParams + expErr bool + }{ + "valid request": { + msg: types.MsgUpdateParams{ + Authority: types.DefaultAuthority().String(), + Params: types.DefaultParams(), + }, + expErr: false, + }, + "invalid authority": { + msg: types.MsgUpdateParams{ + Authority: "invalid", + Params: types.DefaultParams(), + }, + expErr: true, + }, + "invalid params": { + msg: types.MsgUpdateParams{ + Authority: types.DefaultAuthority().String(), + Params: types.Params{}, + }, + expErr: true, + }, + } + + goctx := sdk.WrapSDKContext(s.ctx) + for name, tc := range tcs { + s.Run(name, func() { + _, err := s.msgServer.UpdateParams(goctx, &tc.msg) + if tc.expErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + } + }) + } +} + +func (s *IntegrationTestSuite) TestTransfer() { + var msg types.MsgTransfer + tcs := map[string]struct { + malleate func() + postExec func() + expErr bool + }{ + "valid request": { + malleate: func() { + msg = types.MsgTransfer{ + Sender: s.guardians[0].String(), + Receiver: "0x1A7C26B0437Aa2d3c8454383650a5D3c35087f91", + Amount: sdk.NewInt(100), + } + }, + expErr: false, + }, + "invalid sender": { + malleate: func() { + msg = types.MsgTransfer{ + Sender: "invalid", + Receiver: "0x1A7C26B0437Aa2d3c8454383650a5D3c35087f91", + Amount: sdk.NewInt(100), + } + }, + expErr: true, + }, + "invalid receiver": { + malleate: func() { + msg = types.MsgTransfer{ + Sender: s.guardians[0].String(), + Receiver: "invalid", + Amount: sdk.NewInt(100), + } + }, + expErr: true, + }, + "insufficient balance": { + malleate: func() { + msg = types.MsgTransfer{ + Sender: s.guardians[0].String(), + Receiver: "0x1A7C26B0437Aa2d3c8454383650a5D3c35087f91", + Amount: sdk.NewInt(int64(0x7FFFFFFFFFFFFFFF)), + } + }, + expErr: true, + }, + "bridge halted": { + malleate: func() { + msg = types.MsgTransfer{ + Sender: s.guardians[0].String(), + Receiver: "0x1A7C26B0437Aa2d3c8454383650a5D3c35087f91", + Amount: sdk.NewInt(100), + } + + _, err := s.msgServer.SetBridgeStatus(sdk.WrapSDKContext(s.ctx), &types.MsgSetBridgeStatus{ + Guardian: s.guardians[0].String(), + Status: types.StatusInactive, + }) + s.Require().NoError(err) + _, err = s.msgServer.SetBridgeStatus(sdk.WrapSDKContext(s.ctx), &types.MsgSetBridgeStatus{ + Guardian: s.guardians[1].String(), + Status: types.StatusInactive, + }) + s.Require().NoError(err) + }, + postExec: func() { + _, err := s.msgServer.SetBridgeStatus(sdk.WrapSDKContext(s.ctx), &types.MsgSetBridgeStatus{ + Guardian: s.guardians[0].String(), + Status: types.StatusActive, + }) + s.Require().NoError(err) + _, err = s.msgServer.SetBridgeStatus(sdk.WrapSDKContext(s.ctx), &types.MsgSetBridgeStatus{ + Guardian: s.guardians[1].String(), + Status: types.StatusActive, + }) + s.Require().NoError(err) + }, + expErr: true, + }, + } + + goctx := sdk.WrapSDKContext(s.ctx) + for name, tc := range tcs { + s.Run(name, func() { + tc.malleate() + _, err := s.msgServer.Transfer(goctx, &msg) + if tc.expErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + } + + if tc.postExec != nil { + tc.postExec() + } + }) + } +} + +func (s *IntegrationTestSuite) TestSuggestRole() { + var msg types.MsgSuggestRole + tcs := map[string]struct { + malleate func() + expErr bool + }{ + "valid request": { + malleate: func() { + msg = types.MsgSuggestRole{ + From: s.guardians[0].String(), + Target: s.guardians[2].String(), + Role: types.RoleOperator, + } + }, + expErr: false, + }, + "invalid proposer": { + malleate: func() { + msg = types.MsgSuggestRole{ + From: "invalid", + Target: s.guardians[2].String(), + Role: types.RoleOperator, + } + }, + expErr: true, + }, + "invalid target address": { + malleate: func() { + msg = types.MsgSuggestRole{ + From: s.guardians[0].String(), + Target: "invalid", + Role: types.RoleOperator, + } + }, + expErr: true, + }, + "unsupported role": { + malleate: func() { + msg = types.MsgSuggestRole{ + From: s.guardians[0].String(), + Target: s.guardians[1].String(), + Role: types.Role(10), + } + }, + expErr: true, + }, + "target already has same role": { + malleate: func() { + msg = types.MsgSuggestRole{ + From: s.guardians[0].String(), + Target: s.guardians[1].String(), + Role: types.RoleGuardian, + } + }, + expErr: true, + }, + } + + goctx := sdk.WrapSDKContext(s.ctx) + for name, tc := range tcs { + s.Run(name, func() { + tc.malleate() + _, err := s.msgServer.SuggestRole(goctx, &msg) + if tc.expErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + } + }) + } +} + +func (s *IntegrationTestSuite) TestAddVoteForRole() { + goctx := sdk.WrapSDKContext(s.ctx) + _, err := s.msgServer.SuggestRole(goctx, &types.MsgSuggestRole{ + From: s.guardians[0].String(), + Target: s.guardians[2].String(), + Role: types.RoleOperator, + }) + s.Require().NoError(err) + const proposalID = 5 + + var msg types.MsgAddVoteForRole + tcs := map[string]struct { + malleate func() + expErr bool + }{ + "valid request": { + malleate: func() { + msg = types.MsgAddVoteForRole{ + From: s.guardians[0].String(), + ProposalId: proposalID, + Option: types.OptionYes, + } + }, + expErr: false, + }, + "invalid voter": { + malleate: func() { + msg = types.MsgAddVoteForRole{ + From: "invalid", + ProposalId: 0, + Option: types.OptionYes, + } + }, + expErr: true, + }, + "unauthorized voter": { + malleate: func() { + msg = types.MsgAddVoteForRole{ + From: s.operator.String(), + ProposalId: proposalID, + Option: types.OptionYes, + } + }, + expErr: true, + }, + "invalid proposal id": { + malleate: func() { + msg = types.MsgAddVoteForRole{ + From: s.guardians[0].String(), + ProposalId: 100, + Option: types.OptionYes, + } + }, + expErr: true, + }, + "invalid option": { + malleate: func() { + msg = types.MsgAddVoteForRole{ + From: s.guardians[0].String(), + ProposalId: 0, + Option: types.VoteOption(10), + } + }, + expErr: true, + }, + } + + for name, tc := range tcs { + s.Run(name, func() { + tc.malleate() + _, err := s.msgServer.AddVoteForRole(goctx, &msg) + if tc.expErr { + s.Require().Error(err) + fmt.Println(err) + } else { + s.Require().NoError(err) + } + }) + } +} + +func (s *IntegrationTestSuite) TestSetBridgeStatus() { + var msg types.MsgSetBridgeStatus + tcs := map[string]struct { + malleate func() + postExec func(ctx context.Context) + expErr bool + }{ + "valid request": { + malleate: func() { + msg = types.MsgSetBridgeStatus{ + Guardian: s.guardians[0].String(), + Status: types.StatusInactive, + } + }, + postExec: func(ctx context.Context) { + msg = types.MsgSetBridgeStatus{ + Guardian: s.guardians[0].String(), + Status: types.StatusActive, + } + + _, err := s.msgServer.SetBridgeStatus(ctx, &msg) + s.Require().NoError(err) + }, + expErr: false, + }, + "invalid guardian address": { + malleate: func() { + msg = types.MsgSetBridgeStatus{ + Guardian: "invalid", + Status: types.StatusInactive, + } + }, + expErr: true, + }, + "equal to current status": { + malleate: func() { + msg = types.MsgSetBridgeStatus{ + Guardian: s.guardians[1].String(), + Status: types.StatusActive, + } + }, + expErr: true, + }, + "invalid bridge status": { + malleate: func() { + msg = types.MsgSetBridgeStatus{ + Guardian: s.guardians[1].String(), + Status: types.BridgeStatus(10), + } + }, + expErr: true, + }, + } + + goctx := sdk.WrapSDKContext(s.ctx) + for name, tc := range tcs { + s.Run(name, func() { + tc.malleate() + _, err := s.msgServer.SetBridgeStatus(goctx, &msg) + if tc.expErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + } + }) + } +} diff --git a/x/fbridge/types/fbridge_test.go b/x/fbridge/types/fbridge_test.go new file mode 100644 index 0000000000..677820aa4c --- /dev/null +++ b/x/fbridge/types/fbridge_test.go @@ -0,0 +1,110 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/Finschia/finschia-sdk/x/fbridge/types" +) + +func TestIsValidRole(t *testing.T) { + t.Parallel() + + tcs := map[string]struct { + role types.Role + expPass bool + }{ + "valid role - guardian": { + role: types.RoleGuardian, + expPass: true, + }, + "valid role - operator": { + role: types.RoleOperator, + expPass: true, + }, + "valid role - judge": { + role: types.RoleJudge, + expPass: true, + }, + "invalid role": { + role: types.Role(10), + expPass: false, + }, + } + + for name, tc := range tcs { + t.Run(name, func(t *testing.T) { + err := types.IsValidRole(tc.role) + if tc.expPass { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } +} + +func TestIsValidVoteOption(t *testing.T) { + t.Parallel() + + tcs := map[string]struct { + option types.VoteOption + expPass bool + }{ + "valid option - yes": { + option: types.OptionYes, + expPass: true, + }, + "valid option - no": { + option: types.OptionNo, + expPass: true, + }, + "invalid option": { + option: types.VoteOption(10), + expPass: false, + }, + } + + for name, tc := range tcs { + t.Run(name, func(t *testing.T) { + err := types.IsValidVoteOption(tc.option) + if tc.expPass { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } +} + +func TestIsValidBridgeStatus(t *testing.T) { + tcs := map[string]struct { + status types.BridgeStatus + expPass bool + }{ + "valid status - active": { + status: types.StatusActive, + expPass: true, + }, + "valid status - inactive": { + status: types.StatusInactive, + expPass: true, + }, + "invalid status": { + status: types.BridgeStatus(10), + expPass: false, + }, + } + + for name, tc := range tcs { + t.Run(name, func(t *testing.T) { + err := types.IsValidBridgeStatus(tc.status) + if tc.expPass { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } +} diff --git a/x/fbridge/types/keys_test.go b/x/fbridge/types/keys_test.go new file mode 100644 index 0000000000..303f78d36f --- /dev/null +++ b/x/fbridge/types/keys_test.go @@ -0,0 +1,32 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/Finschia/finschia-sdk/types" + "github.com/Finschia/finschia-sdk/x/fbridge/types" +) + +func TestFbridgeKeys(t *testing.T) { + require.Equal(t, []byte{types.KeySeqToBlocknumPrefix[0], 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, types.SeqToBlocknumKey(1)) + require.Equal(t, []byte{types.KeyProposalPrefix[0], 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, types.ProposalKey(1)) + require.Equal(t, []byte{types.KeyProposalVotePrefix[0], 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, types.VotesKey(1)) + + vvkey := types.VoterVoteKey(1, []byte("voter")) + require.Equal(t, []byte{types.KeyProposalVotePrefix[0], 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x76, 0x6f, 0x74, 0x65, 0x72}, vvkey) + pid, voter := types.SplitVoterVoteKey(vvkey) + require.Equal(t, uint64(1), pid) + require.Equal(t, sdk.AccAddress("voter"), voter) + + rkey := types.RoleKey([]byte("assignee")) + require.Equal(t, []byte{types.KeyRolePrefix[0], 0x8, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x65}, rkey) + role := types.SplitRoleKey(rkey) + require.Equal(t, sdk.AccAddress("assignee"), role) + + bskey := types.BridgeSwitchKey([]byte("guardian")) + require.Equal(t, []byte{types.KeyBridgeSwitchPrefix[0], 0x8, 0x67, 0x75, 0x61, 0x72, 0x64, 0x69, 0x61, 0x6e}, bskey) + guardian := types.SplitBridgeSwitchKey(bskey) + require.Equal(t, sdk.AccAddress("guardian"), guardian) +} diff --git a/x/fbridge/types/params_test.go b/x/fbridge/types/params_test.go index 025ee7bbd0..258be68682 100644 --- a/x/fbridge/types/params_test.go +++ b/x/fbridge/types/params_test.go @@ -8,7 +8,73 @@ import ( "github.com/Finschia/finschia-sdk/x/fbridge/types" ) +func TestValidateParams(t *testing.T) { + t.Parallel() + + tcs := map[string]struct { + malleate func(p *types.Params) + expErr bool + }{ + "valid params": { + expErr: false, + }, + "invalid guardian trust level": { + malleate: func(p *types.Params) { + p.GuardianTrustLevel = types.Fraction{Numerator: 3, Denominator: 2} + }, + expErr: true, + }, + "invalid operator trust level": { + malleate: func(p *types.Params) { + p.OperatorTrustLevel = types.Fraction{Numerator: 0, Denominator: 2} + }, + expErr: true, + }, + "invalid judge trust level": { + malleate: func(p *types.Params) { + p.JudgeTrustLevel = types.Fraction{Numerator: 0, Denominator: 0} + }, + expErr: true, + }, + "invalid proposal period": { + malleate: func(p *types.Params) { + p.ProposalPeriod = 0 + }, + expErr: true, + }, + "invalid timelock period": { + malleate: func(p *types.Params) { + p.TimelockPeriod = 0 + }, + expErr: true, + }, + "invalid target denom": { + malleate: func(p *types.Params) { + p.TargetDenom = "invalid_denom" + }, + expErr: true, + }, + } + + for name, tc := range tcs { + t.Run(name, func(t *testing.T) { + p := types.DefaultParams() + if tc.malleate != nil { + tc.malleate(&p) + } + if tc.expErr { + err := p.ValidateParams() + require.Error(t, err) + } else { + require.NoError(t, p.ValidateParams()) + } + }) + } +} + func TestCheckTrustLevelThreshold(t *testing.T) { + t.Parallel() + tcs := map[string]struct { total uint64 current uint64