From e58cb3fa1a0da8a4504dc9ca6f121264cb6ab6d5 Mon Sep 17 00:00:00 2001 From: jaeseung-bae <119839167+jaeseung-bae@users.noreply.github.com> Date: Thu, 13 Jun 2024 20:37:57 +0900 Subject: [PATCH] chore: add additional testcases for fswap (#1415) * chore: add more test for grpc_query * chore: add more test for config and msg validation * chore: add more test for genesis-related things * chore: add TestMsgSetSwap for msg server * chore: add more test for keeper * chore: fix for lint * chore: update changelog * chore: rename kai to kei * chore: introduce stubGenesisState func for DRY * chore: remove unnecessary code * chore: remove unnecessary code (cherry picked from commit 10726cfaffbbb4f786e0a7503dfe59aee8527812) # Conflicts: # CHANGELOG.md # x/fswap/keeper/genesis_test.go # x/fswap/keeper/grpc_query_test.go # x/fswap/keeper/keeper_test.go # x/fswap/keeper/msg_server_test.go # x/fswap/types/genesis_test.go # x/fswap/types/msgs_test.go --- CHANGELOG.md | 13 + x/fswap/keeper/genesis_test.go | 104 +++++++ x/fswap/keeper/grpc_query_test.go | 317 +++++++++++++++++++ x/fswap/keeper/keeper_test.go | 487 ++++++++++++++++++++++++++++++ x/fswap/keeper/msg_server_test.go | 260 ++++++++++++++++ x/fswap/types/config_test.go | 14 + x/fswap/types/genesis_test.go | 198 ++++++++++++ x/fswap/types/msgs_test.go | 364 ++++++++++++++++++++++ 8 files changed, 1757 insertions(+) create mode 100644 x/fswap/keeper/genesis_test.go create mode 100644 x/fswap/keeper/grpc_query_test.go create mode 100644 x/fswap/keeper/keeper_test.go create mode 100644 x/fswap/keeper/msg_server_test.go create mode 100644 x/fswap/types/config_test.go create mode 100644 x/fswap/types/genesis_test.go create mode 100644 x/fswap/types/msgs_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 172c68c554..959467b185 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,19 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements * (types) [\#1314](https://github.com/Finschia/finschia-sdk/pull/1314) replace IsEqual with Equal +<<<<<<< HEAD +======= +* (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/fswap) [\#1415](https://github.com/Finschia/finschia-sdk/pull/1415) add more testcases for fswap module +>>>>>>> 10726cfaf (chore: add additional testcases for fswap (#1415)) ### Bug Fixes * (x/auth) [#1281](https://github.com/Finschia/finschia-sdk/pull/1281) `ModuleAccount.Validate` now reports a nil `.BaseAccount` instead of panicking. (backport #1274) diff --git a/x/fswap/keeper/genesis_test.go b/x/fswap/keeper/genesis_test.go new file mode 100644 index 0000000000..f9a8e415ed --- /dev/null +++ b/x/fswap/keeper/genesis_test.go @@ -0,0 +1,104 @@ +package keeper_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/Finschia/finschia-sdk/simapp" + "github.com/Finschia/finschia-sdk/testutil/testdata" + sdk "github.com/Finschia/finschia-sdk/types" + "github.com/Finschia/finschia-sdk/x/fswap/types" +) + +func (s *KeeperTestSuite) TestInitAndExportGenesis() { + ctx, _ := s.ctx.CacheContext() + testGenesis := stubGenesisState() + err := s.keeper.InitGenesis(ctx, testGenesis) + s.Require().NoError(err) + + exportGenesis := s.keeper.ExportGenesis(ctx) + fmt.Println(len(exportGenesis.GetSwaps())) + s.Require().Equal(testGenesis, exportGenesis) + s.Require().Equal(testGenesis.GetSwaps(), exportGenesis.GetSwaps()) + s.Require().Equal(testGenesis.GetSwapStats(), exportGenesis.GetSwapStats()) + s.Require().Equal(testGenesis.GetSwappeds(), exportGenesis.GetSwappeds()) +} + +func TestInitGenesis(t *testing.T) { + checkTx := false + app := simapp.Setup(checkTx) + testdata.RegisterInterfaces(app.InterfaceRegistry()) + testdata.RegisterMsgServer(app.MsgServiceRouter(), testdata.MsgServerImpl{}) + ctx := app.BaseApp.NewContext(checkTx, tmproto.Header{}) + keeper := app.FswapKeeper + + tests := []struct { + name string + genState *types.GenesisState + expectedError error + }{ + { + name: "valid", + genState: stubGenesisState(), + expectedError: nil, + }, + { + name: "invalid: swapCount", + genState: func() *types.GenesisState { + state := stubGenesisState() + state.SwapStats.SwapCount = -1 + return state + }(), + expectedError: types.ErrInvalidState, + }, + { + name: "invalid: swaps count exceeds limit", + genState: func() *types.GenesisState { + state := stubGenesisState() + state.Swaps = append(state.Swaps, state.Swaps[0]) + state.Swappeds = append(state.Swappeds, state.Swappeds[0]) + state.SwapStats.SwapCount = 2 + return state + }(), + expectedError: types.ErrCanNotHaveMoreSwap, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := keeper.InitGenesis(ctx, tc.genState) + require.ErrorIs(t, tc.expectedError, err) + }) + } +} + +func stubGenesisState() *types.GenesisState { + testSwapRate, _ := sdk.NewDecFromStr("1234567890") + return &types.GenesisState{ + Swaps: []types.Swap{ + { + FromDenom: "aaa", + ToDenom: "bbb", + AmountCapForToDenom: sdk.NewInt(1234567890000), + SwapRate: testSwapRate, + }, + }, + SwapStats: types.SwapStats{ + SwapCount: 1, + }, + Swappeds: []types.Swapped{ + { + FromCoinAmount: sdk.Coin{ + Denom: "aaa", + Amount: sdk.ZeroInt(), + }, + ToCoinAmount: sdk.Coin{ + Denom: "bbb", + Amount: sdk.ZeroInt(), + }, + }, + }, + } +} diff --git a/x/fswap/keeper/grpc_query_test.go b/x/fswap/keeper/grpc_query_test.go new file mode 100644 index 0000000000..f9499b1dc7 --- /dev/null +++ b/x/fswap/keeper/grpc_query_test.go @@ -0,0 +1,317 @@ +package keeper_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "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" + bank "github.com/Finschia/finschia-sdk/x/bank/types" + "github.com/Finschia/finschia-sdk/x/fswap/keeper" + "github.com/Finschia/finschia-sdk/x/fswap/types" +) + +func TestFSwapQueryTestSuite(t *testing.T) { + suite.Run(t, &FSwapQueryTestSuite{}) +} + +type FSwapQueryTestSuite struct { + suite.Suite + + app *simapp.SimApp + ctx sdk.Context + queryClient types.QueryClient + keeper keeper.Keeper + swap types.Swap + toDenomMetadata bank.Metadata + fromDenom string + toDenom string +} + +func (s *FSwapQueryTestSuite) SetupTest() { + s.app = simapp.Setup(false) + s.ctx = s.app.BaseApp.NewContext(false, tmproto.Header{}) + + queryHelper := baseapp.NewQueryServerTestHelper(s.ctx, s.app.InterfaceRegistry()) + types.RegisterQueryServer(queryHelper, keeper.NewQueryServer(s.app.FswapKeeper)) + s.queryClient = types.NewQueryClient(queryHelper) + s.keeper = s.app.FswapKeeper + + keiSwapRateForCony, err := sdk.NewDecFromStr("148079656000000") + s.Require().NoError(err) + swapCap := sdk.NewInt(1000) + s.Require().NoError(err) + s.fromDenom = "cony" + s.toDenom = "kei" + s.swap = types.Swap{ + FromDenom: s.fromDenom, + ToDenom: s.toDenom, + AmountCapForToDenom: swapCap, + SwapRate: keiSwapRateForCony, + } + s.toDenomMetadata = bank.Metadata{ + Description: "This is metadata for to-coin", + DenomUnits: []*bank.DenomUnit{ + {Denom: s.toDenom, Exponent: 0}, + }, + Base: s.toDenom, + Display: s.toDenom, + Name: "DUMMY", + Symbol: "DUM", + } + err = s.toDenomMetadata.Validate() + s.Require().NoError(err) + + fromDenom := bank.Metadata{ + Description: "This is metadata for from-coin", + DenomUnits: []*bank.DenomUnit{ + {Denom: s.fromDenom, Exponent: 0}, + }, + Base: s.fromDenom, + Display: s.fromDenom, + Name: "FROM", + Symbol: "FROM", + } + err = fromDenom.Validate() + s.Require().NoError(err) + + s.app.BankKeeper.SetDenomMetaData(s.ctx, fromDenom) + err = s.keeper.SetSwap(s.ctx, s.swap, s.toDenomMetadata) + s.Require().NoError(err) +} + +func (s *FSwapQueryTestSuite) TestQuerySwapRequest() { + tests := []struct { + name string + request *types.QuerySwapRequest + expectedResponse *types.QuerySwapResponse + expectedGrpcCode codes.Code + }{ + { + name: "valid", + request: &types.QuerySwapRequest{ + FromDenom: s.fromDenom, + ToDenom: s.toDenom, + }, + expectedResponse: &types.QuerySwapResponse{ + Swap: types.Swap{ + FromDenom: s.swap.FromDenom, + ToDenom: s.swap.ToDenom, + AmountCapForToDenom: s.swap.AmountCapForToDenom, + SwapRate: s.swap.SwapRate, + }, + }, + expectedGrpcCode: codes.OK, + }, + { + name: "invalid: empty fromDenom", + request: &types.QuerySwapRequest{ + FromDenom: "", + ToDenom: s.toDenom, + }, + expectedGrpcCode: codes.InvalidArgument, + }, + { + name: "invalid: empty toDenom", + request: &types.QuerySwapRequest{ + FromDenom: s.fromDenom, + ToDenom: "", + }, + expectedGrpcCode: codes.InvalidArgument, + }, + { + name: "invalid: the same fromDenom and toDenom", + request: &types.QuerySwapRequest{ + FromDenom: s.fromDenom, + ToDenom: s.fromDenom, + }, + expectedGrpcCode: codes.InvalidArgument, + }, + { + name: "invalid: unregistered swap", + request: &types.QuerySwapRequest{ + FromDenom: s.toDenom, + ToDenom: s.fromDenom, + }, + expectedGrpcCode: codes.NotFound, + }, + } + for _, tc := range tests { + s.Run(tc.name, func() { + response, err := s.queryClient.Swap(s.ctx.Context(), tc.request) + s.Require().Equal(tc.expectedResponse, response) + actualGrpcCode := status.Code(err) + s.Require().Equal(tc.expectedGrpcCode, actualGrpcCode, actualGrpcCode.String()) + }) + } +} + +func (s *FSwapQueryTestSuite) TestQuerySwappedRequest() { + tests := []struct { + name string + request *types.QuerySwappedRequest + expectedResponse *types.QuerySwappedResponse + expectedGrpcCode codes.Code + }{ + { + name: "valid", + request: &types.QuerySwappedRequest{ + FromDenom: s.fromDenom, + ToDenom: s.toDenom, + }, + expectedResponse: &types.QuerySwappedResponse{ + FromCoinAmount: sdk.NewCoin(s.fromDenom, sdk.ZeroInt()), + ToCoinAmount: sdk.NewCoin(s.toDenom, sdk.ZeroInt()), + }, + expectedGrpcCode: codes.OK, + }, + { + name: "invalid: empty fromDenom", + request: &types.QuerySwappedRequest{ + FromDenom: "", + ToDenom: s.toDenom, + }, + expectedResponse: nil, + expectedGrpcCode: codes.Unknown, + }, + { + name: "invalid: empty toDenom", + request: &types.QuerySwappedRequest{ + FromDenom: s.fromDenom, + ToDenom: "", + }, + expectedResponse: nil, + expectedGrpcCode: codes.Unknown, + }, + { + name: "invalid: unregistered swap", + request: &types.QuerySwappedRequest{ + FromDenom: s.toDenom, + ToDenom: s.fromDenom, + }, + expectedResponse: nil, + expectedGrpcCode: codes.Unknown, + }, + } + for _, tc := range tests { + s.Run(tc.name, func() { + response, err := s.queryClient.Swapped(s.ctx.Context(), tc.request) + s.Require().Equal(tc.expectedResponse, response) + actualGrpcCode := status.Code(err) + s.Require().Equal(tc.expectedGrpcCode, actualGrpcCode, actualGrpcCode.String()) + }) + } +} + +func (s *FSwapQueryTestSuite) TestQueryTotalSwappableToCoinAmountRequest() { + tests := []struct { + name string + request *types.QueryTotalSwappableToCoinAmountRequest + expectedResponse *types.QueryTotalSwappableToCoinAmountResponse + expectedGrpcCode codes.Code + }{ + { + name: "valid", + request: &types.QueryTotalSwappableToCoinAmountRequest{ + FromDenom: s.fromDenom, + ToDenom: s.toDenom, + }, + expectedResponse: &types.QueryTotalSwappableToCoinAmountResponse{ + SwappableAmount: sdk.NewCoin(s.toDenom, s.swap.AmountCapForToDenom), + }, + expectedGrpcCode: codes.OK, + }, + { + name: "invalid: empty fromDenom", + request: &types.QueryTotalSwappableToCoinAmountRequest{ + FromDenom: "", + ToDenom: s.toDenom, + }, + expectedResponse: nil, + expectedGrpcCode: codes.Unknown, + }, + { + name: "invalid: empty toDenom", + request: &types.QueryTotalSwappableToCoinAmountRequest{ + FromDenom: s.fromDenom, + ToDenom: "", + }, + expectedResponse: nil, + expectedGrpcCode: codes.Unknown, + }, + { + name: "invalid: unregistered swap", + request: &types.QueryTotalSwappableToCoinAmountRequest{ + FromDenom: s.toDenom, + ToDenom: s.fromDenom, + }, + expectedResponse: nil, + expectedGrpcCode: codes.Unknown, + }, + } + for _, tc := range tests { + s.Run(tc.name, func() { + response, err := s.queryClient.TotalSwappableToCoinAmount(s.ctx.Context(), tc.request) + s.Require().Equal(tc.expectedResponse, response) + actualGrpcCode := status.Code(err) + s.Require().Equal(tc.expectedGrpcCode, actualGrpcCode, actualGrpcCode.String()) + }) + } +} + +func (s *FSwapQueryTestSuite) TestQuerySwapsRequest() { + tests := []struct { + name string + request *types.QuerySwapsRequest + expectedResponse *types.QuerySwapsResponse + expectedGrpcCode codes.Code + }{ + { + name: "valid", + request: &types.QuerySwapsRequest{ + Pagination: nil, + }, + expectedResponse: &types.QuerySwapsResponse{ + Swaps: []types.Swap{ + { + FromDenom: s.swap.FromDenom, + ToDenom: s.swap.ToDenom, + AmountCapForToDenom: s.swap.AmountCapForToDenom, + SwapRate: s.swap.SwapRate, + }, + }, + Pagination: &query.PageResponse{ + NextKey: nil, + Total: 1, + }, + }, + expectedGrpcCode: codes.OK, + }, + { + name: "invalid request", + request: &types.QuerySwapsRequest{ + Pagination: &query.PageRequest{ + Key: []byte("invalid-key"), + Offset: 1, + Limit: 0, + }, + }, + expectedResponse: nil, + expectedGrpcCode: codes.Unknown, + }, + } + for _, tc := range tests { + s.Run(tc.name, func() { + response, err := s.queryClient.Swaps(s.ctx.Context(), tc.request) + s.Require().Equal(tc.expectedResponse, response) + actualGrpcCode := status.Code(err) + s.Require().Equal(tc.expectedGrpcCode, actualGrpcCode, actualGrpcCode.String()) + }) + } +} diff --git a/x/fswap/keeper/keeper_test.go b/x/fswap/keeper/keeper_test.go new file mode 100644 index 0000000000..f5f45c3c9f --- /dev/null +++ b/x/fswap/keeper/keeper_test.go @@ -0,0 +1,487 @@ +package keeper_test + +import ( + "context" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/Finschia/finschia-sdk/crypto/keys/secp256k1" + "github.com/Finschia/finschia-sdk/simapp" + "github.com/Finschia/finschia-sdk/testutil/testdata" + sdk "github.com/Finschia/finschia-sdk/types" + sdkerrors "github.com/Finschia/finschia-sdk/types/errors" + authtypes "github.com/Finschia/finschia-sdk/x/auth/types" + bank "github.com/Finschia/finschia-sdk/x/bank/types" + "github.com/Finschia/finschia-sdk/x/foundation" + "github.com/Finschia/finschia-sdk/x/fswap/keeper" + "github.com/Finschia/finschia-sdk/x/fswap/testutil" + "github.com/Finschia/finschia-sdk/x/fswap/types" + govtypes "github.com/Finschia/finschia-sdk/x/gov/types" + minttypes "github.com/Finschia/finschia-sdk/x/mint/types" +) + +type KeeperTestSuite struct { + suite.Suite + + ctx sdk.Context + goCtx context.Context + keeper keeper.Keeper + queryServer types.QueryServer + msgServer types.MsgServer + + accWithFromCoin sdk.AccAddress + accWithToCoin sdk.AccAddress + initBalance sdk.Int + + swap types.Swap + toDenomMetadata bank.Metadata +} + +func (s *KeeperTestSuite) createRandomAccounts(n int) []sdk.AccAddress { + seenAddresses := make(map[string]bool, n) + addresses := make([]sdk.AccAddress, n) + for i := range addresses { + var address sdk.AccAddress + for { + pk := secp256k1.GenPrivKey().PubKey() + address = sdk.AccAddress(pk.Address()) + if !seenAddresses[address.String()] { + seenAddresses[address.String()] = true + break + } + } + addresses[i] = address + } + return addresses +} + +func (s *KeeperTestSuite) SetupTest() { + checkTx := false + app := simapp.Setup(checkTx) + testdata.RegisterInterfaces(app.InterfaceRegistry()) + testdata.RegisterMsgServer(app.MsgServiceRouter(), testdata.MsgServerImpl{}) + s.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{}) + s.goCtx = sdk.WrapSDKContext(s.ctx) + s.keeper = app.FswapKeeper + s.queryServer = keeper.NewQueryServer(s.keeper) + s.msgServer = keeper.NewMsgServer(s.keeper) + + numAcc := int64(2) + s.initBalance = sdk.NewInt(123456789) + pebSwapRateForCony, err := sdk.NewDecFromStr("148079656000000") + s.Require().NoError(err) + swapCap := sdk.NewIntFromBigInt(pebSwapRateForCony.Mul(s.initBalance.ToDec()).BigInt()) + swapCap = swapCap.Mul(sdk.NewInt(numAcc)) + s.Require().NoError(err) + s.swap = types.Swap{ + FromDenom: "fromdenom", + ToDenom: "todenom", + AmountCapForToDenom: swapCap, + SwapRate: pebSwapRateForCony, + } + s.toDenomMetadata = bank.Metadata{ + Description: "This is metadata for to-coin", + DenomUnits: []*bank.DenomUnit{ + {Denom: s.swap.ToDenom, Exponent: 0}, + }, + Base: s.swap.ToDenom, + Display: s.swap.ToDenom, + Name: "DUMMY", + Symbol: "DUM", + } + err = s.toDenomMetadata.Validate() + s.Require().NoError(err) + + fromDenom := bank.Metadata{ + Description: "This is metadata for from-coin", + DenomUnits: []*bank.DenomUnit{ + {Denom: s.swap.FromDenom, Exponent: 0}, + }, + Base: s.swap.FromDenom, + Display: s.swap.FromDenom, + Name: "FROM", + Symbol: "FROM", + } + err = fromDenom.Validate() + s.Require().NoError(err) + + app.BankKeeper.SetDenomMetaData(s.ctx, fromDenom) + s.createAccountsWithInitBalance(app) + app.AccountKeeper.GetModuleAccount(s.ctx, types.ModuleName) +} + +func (s *KeeperTestSuite) createAccountsWithInitBalance(app *simapp.SimApp) { + addresses := []*sdk.AccAddress{ + &s.accWithFromCoin, + &s.accWithToCoin, + } + for i, address := range s.createRandomAccounts(len(addresses)) { + *addresses[i] = address + } + minter := app.AccountKeeper.GetModuleAccount(s.ctx, minttypes.ModuleName).GetAddress() + fromAmount := sdk.NewCoins(sdk.NewCoin(s.swap.GetFromDenom(), s.initBalance)) + s.Require().NoError(app.BankKeeper.MintCoins(s.ctx, minttypes.ModuleName, fromAmount)) + s.Require().NoError(app.BankKeeper.SendCoins(s.ctx, minter, s.accWithFromCoin, fromAmount)) + + toAmount := sdk.NewCoins(sdk.NewCoin(s.swap.GetToDenom(), s.initBalance)) + s.Require().NoError(app.BankKeeper.MintCoins(s.ctx, minttypes.ModuleName, toAmount)) + s.Require().NoError(app.BankKeeper.SendCoins(s.ctx, minter, s.accWithToCoin, toAmount)) +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, &KeeperTestSuite{}) +} + +func TestNewKeeper(t *testing.T) { + app := simapp.Setup(false) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + authKeeper := testutil.NewMockAccountKeeper(ctrl) + testCases := map[string]struct { + malleate func() + isPanic bool + }{ + "fswap module account has not been set": { + malleate: func() { + authKeeper.EXPECT().GetModuleAddress(types.ModuleName).Return(nil).Times(1) + keeper.NewKeeper(app.AppCodec(), sdk.NewKVStoreKey(types.StoreKey), types.DefaultConfig(), types.DefaultAuthority().String(), authKeeper, app.BankKeeper) + }, + isPanic: true, + }, + "fswap authority must be the gov or foundation module account": { + malleate: func() { + authKeeper.EXPECT().GetModuleAddress(types.ModuleName).Return(authtypes.NewModuleAddress(types.ModuleName)).Times(1) + keeper.NewKeeper(app.AppCodec(), sdk.NewKVStoreKey(types.StoreKey), types.DefaultConfig(), authtypes.NewModuleAddress("invalid").String(), authKeeper, app.BankKeeper) + }, + isPanic: true, + }, + "success - gov authority": { + malleate: func() { + authKeeper.EXPECT().GetModuleAddress(types.ModuleName).Return(authtypes.NewModuleAddress(types.ModuleName)).Times(1) + keeper.NewKeeper(app.AppCodec(), sdk.NewKVStoreKey(types.StoreKey), types.DefaultConfig(), authtypes.NewModuleAddress(govtypes.ModuleName).String(), authKeeper, app.BankKeeper) + }, + isPanic: false, + }, + "success - foundation authority": { + malleate: func() { + authKeeper.EXPECT().GetModuleAddress(types.ModuleName).Return(authtypes.NewModuleAddress(types.ModuleName)).Times(1) + keeper.NewKeeper(app.AppCodec(), sdk.NewKVStoreKey(types.StoreKey), types.DefaultConfig(), authtypes.NewModuleAddress(foundation.ModuleName).String(), authKeeper, app.BankKeeper) + }, + isPanic: false, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + if tc.isPanic { + require.Panics(t, tc.malleate) + } else { + tc.malleate() + } + }) + } +} + +func (s *KeeperTestSuite) TestSwap() { + swap2ExpectedAmount, ok := sdk.NewIntFromString("296159312000000") + s.Require().True(ok) + swap100ExpectedAmount, ok := sdk.NewIntFromString("14807965600000000") + s.Require().True(ok) + swapAllExpectedBalance, ok := sdk.NewIntFromString("18281438845984584000000") + s.Require().True(ok) + testCases := map[string]struct { + from sdk.AccAddress + amountToSwap sdk.Coin + toDenom string + expectedAmount sdk.Int + shouldThrowError bool + expectedError error + }{ + "swap 2 from-denom": { + s.accWithFromCoin, + sdk.NewCoin(s.swap.GetFromDenom(), sdk.NewInt(2)), + s.swap.GetToDenom(), + swap2ExpectedAmount, + false, + nil, + }, + "swap some": { + s.accWithFromCoin, + sdk.NewCoin(s.swap.GetFromDenom(), sdk.NewInt(100)), + s.swap.GetToDenom(), + swap100ExpectedAmount, + false, + nil, + }, + "swap all the balance": { + s.accWithFromCoin, + sdk.NewCoin(s.swap.GetFromDenom(), s.initBalance), + s.swap.GetToDenom(), + swapAllExpectedBalance, + false, + nil, + }, + "swap without holding enough balance": { + s.accWithFromCoin, + sdk.NewCoin(s.swap.GetFromDenom(), sdk.OneInt().Add(s.initBalance)), + s.swap.GetToDenom(), + sdk.ZeroInt(), + true, + sdkerrors.ErrInsufficientFunds, + }, + "account holding new coin only": { + s.accWithToCoin, + sdk.NewCoin(s.swap.GetFromDenom(), sdk.NewInt(100)), + s.swap.GetToDenom(), + sdk.ZeroInt(), + true, + sdkerrors.ErrInsufficientFunds, + }, + "invalid: unregistered swap": { + s.accWithToCoin, + sdk.NewCoin(s.swap.GetFromDenom(), sdk.NewInt(100)), + "nono", + sdk.ZeroInt(), + true, + sdkerrors.ErrNotFound, + }, + "invalid: amount exceed": { + s.accWithToCoin, + sdk.NewCoin(s.swap.GetFromDenom(), s.swap.AmountCapForToDenom.Add(sdk.OneInt())), + s.swap.GetToDenom(), + sdk.ZeroInt(), + true, + types.ErrExceedSwappableToCoinAmount, + }, + } + for name, tc := range testCases { + s.Run(name, func() { + ctx, _ := s.ctx.CacheContext() + err := s.keeper.SetSwap(ctx, s.swap, s.toDenomMetadata) + s.Require().NoError(err) + + err = s.keeper.Swap(ctx, tc.from, tc.amountToSwap, tc.toDenom) + if tc.shouldThrowError { + s.Require().ErrorIs(err, tc.expectedError) + return + } + s.Require().NoError(err) + + actualAmount := s.keeper.GetBalance(ctx, tc.from, s.swap.GetToDenom()).Amount + s.Require().Equal(tc.expectedAmount, actualAmount) + }) + } +} + +func (s *KeeperTestSuite) TestSetSwap() { + ctrl := gomock.NewController(s.T()) + defer ctrl.Finish() + bankKeeper := testutil.NewMockBankKeeper(ctrl) + + testCases := map[string]struct { + swap types.Swap + toDenomMeta bank.Metadata + malleate func() + expectedError error + expectedEvents sdk.Events + }{ + "valid": { + types.Swap{ + FromDenom: "fromdenom", + ToDenom: "todenom", + AmountCapForToDenom: sdk.OneInt(), + SwapRate: sdk.OneDec(), + }, + s.toDenomMetadata, + func() { + bankKeeper.EXPECT().GetDenomMetaData(gomock.Any(), "fromdenom").Return(bank.Metadata{}, true).Times(1) + bankKeeper.EXPECT().GetDenomMetaData(gomock.Any(), "todenom").Return(bank.Metadata{}, false).Times(1) + bankKeeper.EXPECT().SetDenomMetaData(gomock.Any(), s.toDenomMetadata).Times(1) + }, + nil, + sdk.Events{ + sdk.Event{ + Type: "lbm.fswap.v1.EventSetSwap", + Attributes: []abci.EventAttribute{ + { + Key: []byte("swap"), + Value: []uint8{0x7b, 0x22, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x22, 0x3a, 0x22, 0x66, 0x72, 0x6f, 0x6d, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x22, 0x2c, 0x22, 0x74, 0x6f, 0x5f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x22, 0x3a, 0x22, 0x74, 0x6f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x22, 0x2c, 0x22, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x63, 0x61, 0x70, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x74, 0x6f, 0x5f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x22, 0x3a, 0x22, 0x31, 0x22, 0x2c, 0x22, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x22, 0x31, 0x2e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x22, 0x7d}, + Index: false, + }, + }, + }, + sdk.Event{ + Type: "lbm.fswap.v1.EventAddDenomMetadata", + Attributes: []abci.EventAttribute{ + { + Key: []byte("metadata"), + Value: []uint8{0x7b, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x6f, 0x2d, 0x63, 0x6f, 0x69, 0x6e, 0x22, 0x2c, 0x22, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x22, 0x3a, 0x22, 0x74, 0x6f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x22, 0x2c, 0x22, 0x65, 0x78, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x22, 0x3a, 0x5b, 0x5d, 0x7d, 0x5d, 0x2c, 0x22, 0x62, 0x61, 0x73, 0x65, 0x22, 0x3a, 0x22, 0x74, 0x6f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x22, 0x2c, 0x22, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x22, 0x3a, 0x22, 0x74, 0x6f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x22, 0x2c, 0x22, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x22, 0x44, 0x55, 0x4d, 0x4d, 0x59, 0x22, 0x2c, 0x22, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x22, 0x3a, 0x22, 0x44, 0x55, 0x4d, 0x22, 0x7d}, + Index: false, + }, + }, + }, + }, + }, + "to-denom metadata has been stored": { + types.Swap{ + FromDenom: "fromdenom", + ToDenom: "todenom", + AmountCapForToDenom: sdk.OneInt(), + SwapRate: sdk.OneDec(), + }, + s.toDenomMetadata, + func() { + bankKeeper.EXPECT().GetDenomMetaData(gomock.Any(), "fromdenom").Return(bank.Metadata{}, true).Times(1) + bankKeeper.EXPECT().GetDenomMetaData(gomock.Any(), "todenom").Return(s.toDenomMetadata, true).Times(1) + }, + nil, + sdk.Events{ + sdk.Event{ + Type: "lbm.fswap.v1.EventSetSwap", + Attributes: []abci.EventAttribute{ + { + Key: []byte("swap"), + Value: []uint8{0x7b, 0x22, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x22, 0x3a, 0x22, 0x66, 0x72, 0x6f, 0x6d, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x22, 0x2c, 0x22, 0x74, 0x6f, 0x5f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x22, 0x3a, 0x22, 0x74, 0x6f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x22, 0x2c, 0x22, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x63, 0x61, 0x70, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x74, 0x6f, 0x5f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x22, 0x3a, 0x22, 0x31, 0x22, 0x2c, 0x22, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x22, 0x31, 0x2e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x22, 0x7d}, + Index: false, + }, + }, + }, + }, + }, + "from-denom does not exist": { + types.Swap{ + FromDenom: "fakedenom", + ToDenom: "todenom", + AmountCapForToDenom: sdk.OneInt(), + SwapRate: sdk.OneDec(), + }, + s.toDenomMetadata, + func() { + bankKeeper.EXPECT().GetDenomMetaData(gomock.Any(), "fakedenom").Return(bank.Metadata{}, false).Times(1) + }, + sdkerrors.ErrInvalidRequest, + sdk.Events{}, + }, + "to-denom metadata change not allowed": { + types.Swap{ + FromDenom: "fromdenom", + ToDenom: "change", + AmountCapForToDenom: sdk.OneInt(), + SwapRate: sdk.OneDec(), + }, + bank.Metadata{ + Description: s.toDenomMetadata.Description, + DenomUnits: s.toDenomMetadata.DenomUnits, + Base: "change", + Display: "change", + Name: s.toDenomMetadata.Name, + Symbol: s.toDenomMetadata.Symbol, + }, + func() { + bankKeeper.EXPECT().GetDenomMetaData(gomock.Any(), "fromdenom").Return(bank.Metadata{}, true).Times(1) + bankKeeper.EXPECT().GetDenomMetaData(gomock.Any(), "change").Return(s.toDenomMetadata, true).Times(1) + }, + sdkerrors.ErrInvalidRequest, + sdk.Events{}, + }, + } + for name, tc := range testCases { + s.Run(name, func() { + ctx, _ := s.ctx.CacheContext() + tc.malleate() + s.keeper.BankKeeper = bankKeeper + + err := s.keeper.SetSwap(ctx, tc.swap, tc.toDenomMeta) + if tc.expectedError != nil { + s.Require().ErrorIs(err, tc.expectedError) + return + } + events := ctx.EventManager().Events() + s.Require().Equal(tc.expectedEvents, events) + }) + } +} + +func (s *KeeperTestSuite) TestSwapValidateBasic() { + testCases := map[string]struct { + swap types.Swap + shouldThrowError bool + expectedError error + }{ + "valid": { + types.Swap{ + FromDenom: "fromD", + ToDenom: "toD", + AmountCapForToDenom: sdk.OneInt(), + SwapRate: sdk.OneDec(), + }, + false, + nil, + }, + "invalid empty from-denom": { + types.Swap{ + FromDenom: "", + ToDenom: "toD", + AmountCapForToDenom: sdk.OneInt(), + SwapRate: sdk.OneDec(), + }, + true, + sdkerrors.ErrInvalidRequest, + }, + "invalid empty to-denom": { + types.Swap{ + FromDenom: "fromD", + ToDenom: "", + AmountCapForToDenom: sdk.OneInt(), + SwapRate: sdk.OneDec(), + }, + true, + sdkerrors.ErrInvalidRequest, + }, + "invalid zero amount cap for to-denom": { + types.Swap{ + FromDenom: "fromD", + ToDenom: "toD", + AmountCapForToDenom: sdk.ZeroInt(), + SwapRate: sdk.OneDec(), + }, + true, + sdkerrors.ErrInvalidRequest, + }, + "invalid zero swap-rate": { + types.Swap{ + FromDenom: "fromD", + ToDenom: "toD", + AmountCapForToDenom: sdk.OneInt(), + SwapRate: sdk.ZeroDec(), + }, + true, + sdkerrors.ErrInvalidRequest, + }, + "invalid the same from-denom and to-denom": { + types.Swap{ + FromDenom: "same", + ToDenom: "same", + AmountCapForToDenom: sdk.OneInt(), + SwapRate: sdk.OneDec(), + }, + true, + sdkerrors.ErrInvalidRequest, + }, + } + for name, tc := range testCases { + s.Run(name, func() { + err := tc.swap.ValidateBasic() + if tc.shouldThrowError { + s.Require().ErrorIs(err, tc.expectedError) + return + } + s.Require().NoError(err) + }) + } +} diff --git a/x/fswap/keeper/msg_server_test.go b/x/fswap/keeper/msg_server_test.go new file mode 100644 index 0000000000..81ce7529ac --- /dev/null +++ b/x/fswap/keeper/msg_server_test.go @@ -0,0 +1,260 @@ +package keeper_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/Finschia/finschia-sdk/simapp" + "github.com/Finschia/finschia-sdk/testutil/testdata" + sdk "github.com/Finschia/finschia-sdk/types" + sdkerrors "github.com/Finschia/finschia-sdk/types/errors" + authtypes "github.com/Finschia/finschia-sdk/x/auth/types" + bank "github.com/Finschia/finschia-sdk/x/bank/types" + "github.com/Finschia/finschia-sdk/x/foundation" + fkeeper "github.com/Finschia/finschia-sdk/x/fswap/keeper" + "github.com/Finschia/finschia-sdk/x/fswap/types" +) + +func (s *KeeperTestSuite) TestMsgSwap() { + swap2ExpectedAmount, ok := sdk.NewIntFromString("296159312000000") + s.Require().True(ok) + swap100ExpectedAmount, ok := sdk.NewIntFromString("14807965600000000") + s.Require().True(ok) + swapAllExpectedBalance, ok := sdk.NewIntFromString("18281438845984584000000") + s.Require().True(ok) + testCases := map[string]struct { + request *types.MsgSwap + expectedAmount sdk.Int + shouldThrowError bool + expectedError error + }{ + "swap 2 from-denom": { + &types.MsgSwap{ + FromAddress: s.accWithFromCoin.String(), + FromCoinAmount: sdk.NewCoin(s.swap.GetFromDenom(), sdk.NewInt(2)), + ToDenom: s.swap.GetToDenom(), + }, + swap2ExpectedAmount, + false, + nil, + }, + "swap some": { + &types.MsgSwap{ + FromAddress: s.accWithFromCoin.String(), + FromCoinAmount: sdk.NewCoin(s.swap.GetFromDenom(), sdk.NewInt(100)), + ToDenom: s.swap.GetToDenom(), + }, + swap100ExpectedAmount, + false, + nil, + }, + "swap all the balance": { + &types.MsgSwap{ + FromAddress: s.accWithFromCoin.String(), + FromCoinAmount: sdk.NewCoin(s.swap.GetFromDenom(), s.initBalance), + ToDenom: s.swap.GetToDenom(), + }, + swapAllExpectedBalance, + false, + nil, + }, + "account holding to-coin only": { + &types.MsgSwap{ + FromAddress: s.accWithToCoin.String(), + FromCoinAmount: sdk.NewCoin(s.swap.GetFromDenom(), sdk.NewInt(100)), + ToDenom: s.swap.GetToDenom(), + }, + sdk.ZeroInt(), + true, + sdkerrors.ErrInsufficientFunds, + }, + } + for name, tc := range testCases { + s.Run(name, func() { + ctx, _ := s.ctx.CacheContext() + err := s.keeper.SetSwap(ctx, s.swap, s.toDenomMetadata) + s.Require().NoError(err) + + swapResponse, err := s.msgServer.Swap(sdk.WrapSDKContext(ctx), tc.request) + if tc.shouldThrowError { + s.Require().ErrorIs(err, tc.expectedError) + return + } + s.Require().NotNil(swapResponse) + s.Require().NoError(err) + + from, err := sdk.AccAddressFromBech32(tc.request.FromAddress) + s.Require().NoError(err) + actualAmount := s.keeper.GetBalance(ctx, from, tc.request.GetToDenom()).Amount + s.Require().Equal(tc.expectedAmount, actualAmount) + }) + } +} + +func (s *KeeperTestSuite) TestMsgSwapAll() { + swapAllExpectedBalance, ok := sdk.NewIntFromString("18281438845984584000000") + s.Require().True(ok) + testCases := map[string]struct { + request *types.MsgSwapAll + expectedAmount sdk.Int + shouldThrowError bool + expectedError error + }{ + "swapAll": { + &types.MsgSwapAll{ + FromAddress: s.accWithFromCoin.String(), + FromDenom: s.swap.GetFromDenom(), + ToDenom: s.swap.GetToDenom(), + }, + swapAllExpectedBalance, + false, + nil, + }, + "account holding to-coin only": { + &types.MsgSwapAll{ + FromAddress: s.accWithToCoin.String(), + FromDenom: s.swap.GetFromDenom(), + ToDenom: s.swap.GetToDenom(), + }, + s.initBalance, + true, + sdkerrors.ErrInsufficientFunds, + }, + } + for name, tc := range testCases { + s.Run(name, func() { + ctx, _ := s.ctx.CacheContext() + err := s.keeper.SetSwap(ctx, s.swap, s.toDenomMetadata) + s.Require().NoError(err) + + swapResponse, err := s.msgServer.SwapAll(sdk.WrapSDKContext(ctx), tc.request) + if tc.shouldThrowError { + s.Require().ErrorIs(err, tc.expectedError) + return + } + s.Require().NotNil(swapResponse) + s.Require().NoError(err) + + from, err := sdk.AccAddressFromBech32(tc.request.FromAddress) + s.Require().NoError(err) + actualAmount := s.keeper.GetBalance(ctx, from, tc.request.GetToDenom()).Amount + s.Require().Equal(tc.expectedAmount, actualAmount) + }) + } +} + +func TestMsgSetSwap(t *testing.T) { + authority := authtypes.NewModuleAddress(foundation.ModuleName) + checkTx := false + app := simapp.Setup(checkTx) + testdata.RegisterInterfaces(app.InterfaceRegistry()) + testdata.RegisterMsgServer(app.MsgServiceRouter(), testdata.MsgServerImpl{}) + ctx := app.BaseApp.NewContext(checkTx, tmproto.Header{}) + keeper := app.FswapKeeper + msgServer := fkeeper.NewMsgServer(keeper) + fromDenomStr := "cony" + fromDenom := bank.Metadata{ + Description: "This is metadata for from-coin", + DenomUnits: []*bank.DenomUnit{ + {Denom: fromDenomStr, Exponent: 0}, + }, + Base: fromDenomStr, + Display: fromDenomStr, + Name: "FROM", + Symbol: "FROM", + } + app.BankKeeper.SetDenomMetaData(ctx, fromDenom) + + testCases := map[string]struct { + request *types.MsgSetSwap + expectedError error + }{ + "valid": { + request: &types.MsgSetSwap{ + Authority: authority.String(), + Swap: types.Swap{ + FromDenom: fromDenomStr, + ToDenom: "kei", + AmountCapForToDenom: sdk.OneInt(), + SwapRate: sdk.NewDec(1), + }, + ToDenomMetadata: bank.Metadata{ + Description: "desc", + DenomUnits: []*bank.DenomUnit{ + { + Denom: "kei", + Exponent: 0, + Aliases: nil, + }, + }, + Base: "kei", + Display: "kei", + Name: "kei", + Symbol: "KAIA", + }, + }, + expectedError: nil, + }, + "invalid: authority": { + request: &types.MsgSetSwap{ + Authority: "invalid-authority", + Swap: types.Swap{ + FromDenom: fromDenomStr, + ToDenom: "kei", + AmountCapForToDenom: sdk.OneInt(), + SwapRate: sdk.NewDec(1), + }, + ToDenomMetadata: bank.Metadata{ + Description: "desc", + DenomUnits: []*bank.DenomUnit{ + { + Denom: "kei", + Exponent: 0, + Aliases: nil, + }, + }, + Base: "kei", + Display: "kei", + Name: "kei", + Symbol: "KAIA", + }, + }, + expectedError: sdkerrors.ErrUnauthorized, + }, + "invalid: Swap.ToDenom": { + request: &types.MsgSetSwap{ + Authority: authority.String(), + Swap: types.Swap{ + FromDenom: fromDenomStr, + ToDenom: fromDenomStr, + AmountCapForToDenom: sdk.OneInt(), + SwapRate: sdk.NewDec(1), + }, + ToDenomMetadata: bank.Metadata{ + Description: "desc", + DenomUnits: []*bank.DenomUnit{ + { + Denom: "kei", + Exponent: 0, + Aliases: nil, + }, + }, + Base: "kei", + Display: "kei", + Name: "kei", + Symbol: "KAIA", + }, + }, + expectedError: sdkerrors.ErrInvalidRequest, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + context, _ := ctx.CacheContext() + _, err := msgServer.SetSwap(sdk.WrapSDKContext(context), tc.request) + require.ErrorIs(t, err, tc.expectedError) + }) + } +} diff --git a/x/fswap/types/config_test.go b/x/fswap/types/config_test.go new file mode 100644 index 0000000000..90a078a4d7 --- /dev/null +++ b/x/fswap/types/config_test.go @@ -0,0 +1,14 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDefaultConfig(t *testing.T) { + config := DefaultConfig() + + require.Equal(t, false, config.UpdateAllowed) + require.Equal(t, 1, config.MaxSwaps) +} diff --git a/x/fswap/types/genesis_test.go b/x/fswap/types/genesis_test.go new file mode 100644 index 0000000000..289e1626af --- /dev/null +++ b/x/fswap/types/genesis_test.go @@ -0,0 +1,198 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/Finschia/finschia-sdk/types" + authtypes "github.com/Finschia/finschia-sdk/x/auth/types" + "github.com/Finschia/finschia-sdk/x/foundation" + "github.com/Finschia/finschia-sdk/x/fswap/types" + govtypes "github.com/Finschia/finschia-sdk/x/gov/types" +) + +func TestGenesisStateValidate(t *testing.T) { + exampleGenesis := func() *types.GenesisState { + testSwapRate, _ := sdk.NewDecFromStr("1234567890") + return &types.GenesisState{ + Swaps: []types.Swap{ + { + FromDenom: "aaa", + ToDenom: "bbb", + AmountCapForToDenom: sdk.NewInt(1234567890000), + SwapRate: testSwapRate, + }, + }, + SwapStats: types.SwapStats{ + SwapCount: 1, + }, + Swappeds: []types.Swapped{ + { + FromCoinAmount: sdk.Coin{ + Denom: "aaa", + Amount: sdk.ZeroInt(), + }, + ToCoinAmount: sdk.Coin{ + Denom: "bbb", + Amount: sdk.ZeroInt(), + }, + }, + }, + } + } + + for _, tc := range []struct { + desc string + genState *types.GenesisState + modify func(*types.GenesisState) + valid bool + }{ + { + desc: "default is valid", + genState: types.DefaultGenesis(), + valid: true, + }, + { + desc: "example is valid", + genState: exampleGenesis(), + valid: true, + }, + { + desc: "SwapCount is nagative in SwapStats is invalid", + genState: exampleGenesis(), + modify: func(gs *types.GenesisState) { + gs.SwapStats.SwapCount = -1 + }, + valid: false, + }, + { + desc: "number of swaps does not match number of Swappeds", + genState: exampleGenesis(), + modify: func(gs *types.GenesisState) { + gs.Swaps = append(gs.Swaps, types.Swap{}) + }, + valid: false, + }, + { + desc: "number of swaps does not match number of Swappeds", + genState: exampleGenesis(), + modify: func(gs *types.GenesisState) { + gs.Swaps = append(gs.Swaps, types.Swap{}) + gs.Swappeds = append(gs.Swappeds, types.Swapped{}) + }, + valid: false, + }, + { + desc: "fromDenom=toDenom in Swap is invalid", + genState: exampleGenesis(), + modify: func(gs *types.GenesisState) { + gs.Swaps[0].ToDenom = "aaa" + }, + valid: false, + }, + { + desc: "AmountCapForToDenom=0 in Swap is invalid", + genState: exampleGenesis(), + modify: func(gs *types.GenesisState) { + gs.Swaps[0].AmountCapForToDenom = sdk.ZeroInt() + }, + valid: false, + }, + { + desc: "SwapRate=0 in Swap is invalid", + genState: exampleGenesis(), + modify: func(gs *types.GenesisState) { + gs.Swaps[0].SwapRate = sdk.ZeroDec() + }, + valid: false, + }, + { + desc: "FromCoinAmount is nagative in Swappeds is invalid", + genState: exampleGenesis(), + modify: func(gs *types.GenesisState) { + gs.Swappeds[0].FromCoinAmount.Amount = sdk.NewInt(-1) + }, + valid: false, + }, + { + desc: "ToCoinAmount is nagative in Swappeds is invalid", + genState: exampleGenesis(), + modify: func(gs *types.GenesisState) { + gs.Swappeds[0].ToCoinAmount.Amount = sdk.NewInt(-1) + }, + valid: false, + }, + { + desc: "Swap in Swaps is invalid", + genState: exampleGenesis(), + modify: func(gs *types.GenesisState) { + gs.Swaps[0].FromDenom = "" + }, + valid: false, + }, + { + desc: "Swapped in Swappeds is invalide ", + genState: exampleGenesis(), + modify: func(gs *types.GenesisState) { + gs.Swappeds[0].FromCoinAmount.Denom = "" + }, + valid: false, + }, + { + desc: "FromCoin in swap and swapped do not correspond", + genState: exampleGenesis(), + modify: func(gs *types.GenesisState) { + gs.Swappeds[0].FromCoinAmount.Denom = "ccc" + }, + valid: false, + }, + { + desc: "ToCoin in swap and swapped do not correspond", + genState: exampleGenesis(), + modify: func(gs *types.GenesisState) { + gs.Swappeds[0].ToCoinAmount.Denom = "ccc" + }, + valid: false, + }, + { + desc: "AmountCapForToDenom has been exceeded is invalid", + genState: exampleGenesis(), + modify: func(gs *types.GenesisState) { + gs.Swappeds[0].ToCoinAmount.Amount = sdk.NewInt(12345678900000000) + }, + valid: false, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + if tc.modify != nil { + tc.modify(tc.genState) + } + err := tc.genState.Validate() + if tc.valid { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } +} + +func TestDefaultAuthority(t *testing.T) { + expectedDefaultAuthority := authtypes.NewModuleAddress(foundation.ModuleName) + + defaultAuthority := types.DefaultAuthority() + + require.Equal(t, expectedDefaultAuthority, defaultAuthority) +} + +func TestAuthorityCandidates(t *testing.T) { + defaultAuthorityCandidates := []sdk.AccAddress{ + authtypes.NewModuleAddress(govtypes.ModuleName), + authtypes.NewModuleAddress(foundation.ModuleName), + } + + authorityCandidates := types.AuthorityCandidates() + + require.Equal(t, defaultAuthorityCandidates, authorityCandidates) +} diff --git a/x/fswap/types/msgs_test.go b/x/fswap/types/msgs_test.go new file mode 100644 index 0000000000..d6091af476 --- /dev/null +++ b/x/fswap/types/msgs_test.go @@ -0,0 +1,364 @@ +package types_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/Finschia/finschia-sdk/crypto/keys/secp256k1" + sdk "github.com/Finschia/finschia-sdk/types" + sdkerrors "github.com/Finschia/finschia-sdk/types/errors" + "github.com/Finschia/finschia-sdk/x/auth/legacy/legacytx" + banktypes "github.com/Finschia/finschia-sdk/x/bank/types" + fswaptypes "github.com/Finschia/finschia-sdk/x/fswap/types" +) + +func TestAminoJSON(t *testing.T) { + tx := legacytx.StdTx{} + + sender := "link15sdc7wdajsps42fky3j6mnvr4tj9fv6w3hkqkj" + + swapRate, _ := sdk.NewDecFromStr("148.079656") + + testCase := map[string]struct { + msg legacytx.LegacyMsg + expectedType string + expected string + }{ + "MsgSwap": { + &fswaptypes.MsgSwap{ + FromAddress: sender, + FromCoinAmount: sdk.Coin{ + Denom: "cony", + Amount: sdk.NewInt(100000), + }, + ToDenom: "pdt", + }, + "/lbm.fswap.v1.MsgSwap", + "{\"account_number\":\"1\",\"chain_id\":\"foo\",\"fee\":{\"amount\":[],\"gas\":\"0\"},\"memo\":\"memo\",\"msgs\":[{\"type\":\"lbm-sdk/MsgSwap\",\"value\":{\"from_address\":\"link15sdc7wdajsps42fky3j6mnvr4tj9fv6w3hkqkj\",\"from_coin_amount\":{\"amount\":\"100000\",\"denom\":\"cony\"},\"to_denom\":\"pdt\"}}],\"sequence\":\"1\",\"timeout_height\":\"1\"}", + }, + "MsgSwapAll": { + &fswaptypes.MsgSwapAll{ + FromAddress: sender, + FromDenom: "cony", + ToDenom: "pdt", + }, + "/lbm.fswap.v1.MsgSwapAll", + "{\"account_number\":\"1\",\"chain_id\":\"foo\",\"fee\":{\"amount\":[],\"gas\":\"0\"},\"memo\":\"memo\",\"msgs\":[{\"type\":\"lbm-sdk/MsgSwapAll\",\"value\":{\"from_address\":\"link15sdc7wdajsps42fky3j6mnvr4tj9fv6w3hkqkj\",\"from_denom\":\"cony\",\"to_denom\":\"pdt\"}}],\"sequence\":\"1\",\"timeout_height\":\"1\"}", + }, + "MsgSetSwap": { + &fswaptypes.MsgSetSwap{ + Authority: sender, + Swap: fswaptypes.Swap{ + FromDenom: "cony", + ToDenom: "pdt", + AmountCapForToDenom: sdk.NewInt(1000000000000000), + SwapRate: swapRate, + }, + ToDenomMetadata: banktypes.Metadata{ + Description: "test", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "kaia", + Exponent: 0, + Aliases: nil, + }, + }, + Base: "kaia", + Display: "kaia", + Name: "Kaia", + Symbol: "KAIA", + }, + }, + "/lbm.fswap.v1.MsgSetSwap", + "{\"account_number\":\"1\",\"chain_id\":\"foo\",\"fee\":{\"amount\":[],\"gas\":\"0\"},\"memo\":\"memo\",\"msgs\":[{\"type\":\"lbm-sdk/MsgSetSwap\",\"value\":{\"authority\":\"link15sdc7wdajsps42fky3j6mnvr4tj9fv6w3hkqkj\",\"swap\":{\"amount_cap_for_to_denom\":\"1000000000000000\",\"from_denom\":\"cony\",\"swap_rate\":\"148.079656000000000000\",\"to_denom\":\"pdt\"},\"to_denom_metadata\":{\"base\":\"kaia\",\"denom_units\":[{\"denom\":\"kaia\"}],\"description\":\"test\",\"display\":\"kaia\",\"name\":\"Kaia\",\"symbol\":\"KAIA\"}}}],\"sequence\":\"1\",\"timeout_height\":\"1\"}", + }, + } + + for name, tc := range testCase { + tc := tc + t.Run(name, func(t *testing.T) { + tx.Msgs = []sdk.Msg{tc.msg} + require.Equal(t, fswaptypes.RouterKey, tc.msg.Route()) + require.Equal(t, tc.expectedType, tc.msg.Type()) + require.Equal(t, tc.expected, string(legacytx.StdSignBytes("foo", 1, 1, 1, legacytx.StdFee{}, []sdk.Msg{tc.msg}, "memo"))) + }) + } +} + +func TestMsgSwapValidate(t *testing.T) { + pk := secp256k1.GenPrivKey().PubKey() + address, err := sdk.Bech32ifyAddressBytes("link", pk.Address()) + if err != nil { + return + } + tests := []struct { + name string + msg *fswaptypes.MsgSwap + expectedError error + }{ + { + name: "valid", + msg: &fswaptypes.MsgSwap{ + FromAddress: address, + FromCoinAmount: sdk.NewCoin("fromDenom", sdk.OneInt()), + ToDenom: "kei", + }, + expectedError: nil, + }, + { + name: "invalid: address", + msg: &fswaptypes.MsgSwap{ + FromAddress: "invalid-address", + FromCoinAmount: sdk.NewCoin("fromDenom", sdk.OneInt()), + ToDenom: "kei", + }, + expectedError: sdkerrors.ErrInvalidAddress, + }, + { + name: "invalid: FromCoinAmount empty denom", + msg: &fswaptypes.MsgSwap{ + FromAddress: address, + FromCoinAmount: sdk.Coin{ + Denom: "", + Amount: sdk.OneInt(), + }, + ToDenom: "kei", + }, + expectedError: sdkerrors.ErrInvalidCoins, + }, + { + name: "invalid: FromCoinAmount zero amount", + msg: &fswaptypes.MsgSwap{ + FromAddress: address, + FromCoinAmount: sdk.Coin{ + Denom: "cony", + Amount: sdk.ZeroInt(), + }, + ToDenom: "kei", + }, + expectedError: sdkerrors.ErrInvalidCoins, + }, + { + name: "invalid: ToDenom", + msg: &fswaptypes.MsgSwap{ + FromAddress: address, + FromCoinAmount: sdk.NewCoin("fromDenom", sdk.OneInt()), + ToDenom: "", + }, + expectedError: sdkerrors.ErrInvalidRequest, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + require.ErrorIs(t, err, tc.expectedError) + }) + } +} + +func TestMsgSwapAllValidate(t *testing.T) { + pk := secp256k1.GenPrivKey().PubKey() + address, err := sdk.Bech32ifyAddressBytes("link", pk.Address()) + if err != nil { + return + } + tests := []struct { + name string + msg *fswaptypes.MsgSwapAll + expectedError error + }{ + { + name: "valid", + msg: &fswaptypes.MsgSwapAll{ + FromAddress: address, + FromDenom: "cony", + ToDenom: "kei", + }, + expectedError: nil, + }, + { + name: "invalid: address", + msg: &fswaptypes.MsgSwapAll{ + FromAddress: "invalid-address", + FromDenom: "cony", + ToDenom: "kei", + }, + expectedError: sdkerrors.ErrInvalidAddress, + }, + { + name: "invalid: FromDenom", + msg: &fswaptypes.MsgSwapAll{ + FromAddress: address, + FromDenom: "", + ToDenom: "kei", + }, + expectedError: sdkerrors.ErrInvalidRequest, + }, + { + name: "invalid: ToDenom", + msg: &fswaptypes.MsgSwapAll{ + FromAddress: address, + FromDenom: "cony", + ToDenom: "", + }, + expectedError: sdkerrors.ErrInvalidRequest, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + require.ErrorIs(t, err, tc.expectedError) + }) + } +} + +func TestMsgSetSwapValidate(t *testing.T) { + pk := secp256k1.GenPrivKey().PubKey() + address, err := sdk.Bech32ifyAddressBytes("link", pk.Address()) + if err != nil { + return + } + tests := []struct { + name string + msg *fswaptypes.MsgSetSwap + expectedError error + }{ + { + name: "valid", + msg: &fswaptypes.MsgSetSwap{ + Authority: address, + Swap: fswaptypes.Swap{ + FromDenom: "cony", + ToDenom: "kei", + AmountCapForToDenom: sdk.OneInt(), + SwapRate: sdk.NewDec(123), + }, + ToDenomMetadata: banktypes.Metadata{ + Description: "desc", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "kei", + Exponent: 0, + Aliases: nil, + }, + }, + Base: "kei", + Display: "kei", + Name: "kei", + Symbol: "KAIA", + }, + }, + expectedError: nil, + }, + { + name: "invalid: address", + msg: &fswaptypes.MsgSetSwap{ + Authority: "invalid-address", + Swap: fswaptypes.Swap{ + FromDenom: "cony", + ToDenom: "kei", + AmountCapForToDenom: sdk.OneInt(), + SwapRate: sdk.NewDec(123), + }, + ToDenomMetadata: banktypes.Metadata{ + Description: "desc", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "kei", + Exponent: 0, + Aliases: nil, + }, + }, + Base: "kei", + Display: "kei", + Name: "kei", + Symbol: "KAIA", + }, + }, + expectedError: sdkerrors.ErrInvalidAddress, + }, + { + name: "invalid: Swap", + msg: &fswaptypes.MsgSetSwap{ + Authority: address, + Swap: fswaptypes.Swap{}, + ToDenomMetadata: banktypes.Metadata{ + Description: "desc", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "kei", + Exponent: 0, + Aliases: nil, + }, + }, + Base: "kei", + Display: "kei", + Name: "kei", + Symbol: "KAIA", + }, + }, + expectedError: sdkerrors.ErrInvalidRequest, + }, + { + name: "invalid: ToDenomMetadata", + msg: &fswaptypes.MsgSetSwap{ + Authority: address, + Swap: fswaptypes.Swap{ + FromDenom: "cony", + ToDenom: "kei", + AmountCapForToDenom: sdk.OneInt(), + SwapRate: sdk.NewDec(123), + }, + ToDenomMetadata: banktypes.Metadata{ + Description: "", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "kei", + Exponent: 0, + Aliases: nil, + }, + }, + }, + }, + expectedError: errors.New("name field cannot be blank"), + }, + + { + name: "invalid: mismatched toDenom", + msg: &fswaptypes.MsgSetSwap{ + Authority: address, + Swap: fswaptypes.Swap{ + FromDenom: "cony", + ToDenom: "kei", + AmountCapForToDenom: sdk.OneInt(), + SwapRate: sdk.NewDec(123), + }, + ToDenomMetadata: banktypes.Metadata{ + Description: "desc", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "fkei", + Exponent: 0, + Aliases: nil, + }, + }, + Base: "fkei", + Display: "fkei", + Name: "fkei", + Symbol: "KAIA", + }, + }, + expectedError: sdkerrors.ErrInvalidRequest, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.expectedError != nil { + require.Contains(t, err.Error(), tc.expectedError.Error()) + } + }) + } +}