diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f7d6bc0d4..0a7aa78832 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (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 ### 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/Makefile b/Makefile index 5eb0153b3d..0f5de5d263 100644 --- a/Makefile +++ b/Makefile @@ -146,7 +146,7 @@ mocks: $(MOCKS_DIR) mockgen -package mocks -destination tests/mocks/grpc_server.go github.com/gogo/protobuf/grpc Server mockgen -package mocks -destination tests/mocks/tendermint_tendermint_libs_log_DB.go github.com/Finschia/ostracon/libs/log Logger mockgen -source=x/stakingplus/expected_keepers.go -package testutil -destination x/stakingplus/testutil/expected_keepers_mocks.go - mockgen -source=x/fswap/types/expected_keepers.go -package testutil -destination x/fswap/testutil/expected_keepers_mocks.go + mockgen -source=x/fswap/keeper/expected_keepers.go -package testutil -destination x/fswap/testutil/expected_keepers_mocks.go mockgen -source=x/fbridge/types/expected_keepers.go -package testutil -destination x/fbridge/testutil/expected_keepers_mocks.go .PHONY: mocks diff --git a/simapp/app.go b/simapp/app.go index f7500fb8fc..1d74d4d9b1 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -347,7 +347,7 @@ func NewSimApp( app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.BaseApp.MsgServiceRouter()) fswapConfig := fswaptypes.DefaultConfig() - app.FswapKeeper = fswapkeeper.NewKeeper(appCodec, keys[fswaptypes.StoreKey], fswapConfig, fswaptypes.DefaultAuthority().String(), app.BankKeeper) + app.FswapKeeper = fswapkeeper.NewKeeper(appCodec, keys[fswaptypes.StoreKey], fswapConfig, fswaptypes.DefaultAuthority().String(), app.AccountKeeper, app.BankKeeper) // register the proposal types govRouter := govtypes.NewRouter() diff --git a/x/fswap/keeper/expected_keepers.go b/x/fswap/keeper/expected_keepers.go index 08934e1c3c..5c7ad92fc4 100644 --- a/x/fswap/keeper/expected_keepers.go +++ b/x/fswap/keeper/expected_keepers.go @@ -5,15 +5,16 @@ import ( banktypes "github.com/Finschia/finschia-sdk/x/bank/types" ) -type ( - BankKeeper interface { - GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin - SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error - SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error - IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error - GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) - SetDenomMetaData(ctx sdk.Context, denomMetaData banktypes.Metadata) - MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error - BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error - } -) +type AccountKeeper interface { + GetModuleAddress(moduleName string) sdk.AccAddress +} +type BankKeeper interface { + GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error + GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) + SetDenomMetaData(ctx sdk.Context, denomMetaData banktypes.Metadata) + MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error +} diff --git a/x/fswap/keeper/genesis_test.go b/x/fswap/keeper/genesis_test.go index c4fa1a0e99..1df6f28c75 100644 --- a/x/fswap/keeper/genesis_test.go +++ b/x/fswap/keeper/genesis_test.go @@ -3,19 +3,45 @@ package keeper_test import ( "fmt" + sdk "github.com/Finschia/finschia-sdk/types" "github.com/Finschia/finschia-sdk/x/fswap/types" ) func (s *KeeperTestSuite) TestInitAndExportGenesis() { ctx, _ := s.ctx.CacheContext() - defaultGenesis := types.DefaultGenesis() - err := s.keeper.InitGenesis(ctx, defaultGenesis) + testSwapRate, _ := sdk.NewDecFromStr("1234567890") + testGenesis := &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(), + }, + }, + }, + } + err := s.keeper.InitGenesis(ctx, testGenesis) s.Require().NoError(err) exportGenesis := s.keeper.ExportGenesis(ctx) fmt.Println(len(exportGenesis.GetSwaps())) - s.Require().Equal(defaultGenesis, exportGenesis) - s.Require().Equal(defaultGenesis.GetSwaps(), exportGenesis.GetSwaps()) - s.Require().Equal(defaultGenesis.GetSwapStats(), exportGenesis.GetSwapStats()) - s.Require().Equal(defaultGenesis.GetSwappeds(), exportGenesis.GetSwappeds()) + 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()) } diff --git a/x/fswap/keeper/grpc_query_test.go b/x/fswap/keeper/grpc_query_test.go index f141459d09..b0c02d7f2e 100644 --- a/x/fswap/keeper/grpc_query_test.go +++ b/x/fswap/keeper/grpc_query_test.go @@ -65,6 +65,17 @@ func (s *FSwapQueryTestSuite) SetupTest() { Symbol: "DUM", } + fromDenom := bank.Metadata{ + Description: "This is metadata for from-coin", + DenomUnits: []*bank.DenomUnit{ + {Denom: s.fromDenom, Exponent: 0}, + }, + Base: s.fromDenom, + Display: "fromcoin", + Name: "FROM", + Symbol: "FROM", + } + s.app.BankKeeper.SetDenomMetaData(s.ctx, fromDenom) err = s.keeper.SetSwap(s.ctx, s.swap, s.toDenomMetadata) s.Require().NoError(err) } diff --git a/x/fswap/keeper/keeper.go b/x/fswap/keeper/keeper.go index e2db580211..073221dee9 100644 --- a/x/fswap/keeper/keeper.go +++ b/x/fswap/keeper/keeper.go @@ -22,11 +22,15 @@ type Keeper struct { BankKeeper } -func NewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey, config types.Config, authority string, bk BankKeeper) Keeper { +func NewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey, config types.Config, authority string, ak AccountKeeper, bk BankKeeper) Keeper { if _, err := sdk.AccAddressFromBech32(authority); err != nil { panic("authority is not a valid acc address") } + if addr := ak.GetModuleAddress(types.ModuleName); addr == nil { + panic("fswap module account has not been set") + } + found := false for _, addr := range types.AuthorityCandidates() { if authority == addr.String() { @@ -125,6 +129,10 @@ func (k Keeper) SetSwap(ctx sdk.Context, swap types.Swap, toDenomMetadata bank.M return types.ErrCanNotHaveMoreSwap.Wrapf("cannot make more swaps, max swaps is %d", k.config.MaxSwaps) } + if _, ok := k.GetDenomMetaData(ctx, swap.FromDenom); !ok { + return sdkerrors.ErrInvalidRequest.Wrap("fromDenom should be existed in chain") + } + if isNewSwap { swapped := types.Swapped{ FromCoinAmount: sdk.Coin{ diff --git a/x/fswap/keeper/keeper_test.go b/x/fswap/keeper/keeper_test.go index 7e63f11818..4d3b68eb06 100644 --- a/x/fswap/keeper/keeper_test.go +++ b/x/fswap/keeper/keeper_test.go @@ -4,6 +4,8 @@ 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" @@ -13,9 +15,13 @@ import ( "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" ) @@ -83,11 +89,22 @@ func (s *KeeperTestSuite) SetupTest() { DenomUnits: []*bank.DenomUnit{ {Denom: s.swap.ToDenom, Exponent: 0}, }, - Base: "dummy", - Display: "dummycoin", + Base: "todenom", + Display: "todenomcoin", Name: "DUMMY", Symbol: "DUM", } + fromDenom := bank.Metadata{ + Description: "This is metadata for from-coin", + DenomUnits: []*bank.DenomUnit{ + {Denom: "fromdenom", Exponent: 0}, + }, + Base: "fromdenom", + Display: "fromdenomcoin", + Name: "DUMMY", + Symbol: "DUM", + } + app.BankKeeper.SetDenomMetaData(s.ctx, fromDenom) s.createAccountsWithInitBalance(app) app.AccountKeeper.GetModuleAccount(s.ctx, types.ModuleName) } @@ -114,6 +131,56 @@ 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) @@ -190,22 +257,30 @@ func (s *KeeperTestSuite) TestSwap() { } 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 - existingMetadata bool - expectedError error - expectedEvents sdk.Events + swap types.Swap + toDenomMeta bank.Metadata + malleate func() + expectedError error + expectedEvents sdk.Events }{ "valid": { types.Swap{ - FromDenom: "fromD", - ToDenom: "toD", + FromDenom: "fromdenom", + ToDenom: "todenom", AmountCapForToDenom: sdk.OneInt(), SwapRate: sdk.OneDec(), }, s.toDenomMetadata, - false, + 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{ @@ -213,7 +288,7 @@ func (s *KeeperTestSuite) TestSetSwap() { 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, 0x44, 0x22, 0x2c, 0x22, 0x74, 0x6f, 0x5f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x22, 0x3a, 0x22, 0x74, 0x6f, 0x44, 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}, + 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, }, }, @@ -223,17 +298,57 @@ func (s *KeeperTestSuite) TestSetSwap() { 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, 0x64, 0x75, 0x6d, 0x6d, 0x79, 0x22, 0x2c, 0x22, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x22, 0x3a, 0x22, 0x64, 0x75, 0x6d, 0x6d, 0x79, 0x63, 0x6f, 0x69, 0x6e, 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}, + 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, 0x63, 0x6f, 0x69, 0x6e, 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: "fromD", - ToDenom: "toD", + FromDenom: "fromdenom", + ToDenom: "change", AmountCapForToDenom: sdk.OneInt(), SwapRate: sdk.OneDec(), }, @@ -245,7 +360,10 @@ func (s *KeeperTestSuite) TestSetSwap() { Name: s.toDenomMetadata.Name, Symbol: s.toDenomMetadata.Symbol, }, - true, + 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{}, }, @@ -253,13 +371,14 @@ func (s *KeeperTestSuite) TestSetSwap() { for name, tc := range testCases { s.Run(name, func() { ctx, _ := s.ctx.CacheContext() - err := s.keeper.SetSwap(ctx, tc.swap, s.toDenomMetadata) - if tc.existingMetadata { - err := s.keeper.SetSwap(ctx, tc.swap, s.toDenomMetadata) + 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 } - s.Require().ErrorIs(err, tc.expectedError) events := ctx.EventManager().Events() s.Require().Equal(tc.expectedEvents, events) }) diff --git a/x/fswap/testutil/expected_keepers_mocks.go b/x/fswap/testutil/expected_keepers_mocks.go index 27cbf452b7..7693d4093f 100644 --- a/x/fswap/testutil/expected_keepers_mocks.go +++ b/x/fswap/testutil/expected_keepers_mocks.go @@ -12,6 +12,43 @@ import ( gomock "github.com/golang/mock/gomock" ) +// MockAccountKeeper is a mock of AccountKeeper interface. +type MockAccountKeeper struct { + ctrl *gomock.Controller + recorder *MockAccountKeeperMockRecorder +} + +// MockAccountKeeperMockRecorder is the mock recorder for MockAccountKeeper. +type MockAccountKeeperMockRecorder struct { + mock *MockAccountKeeper +} + +// NewMockAccountKeeper creates a new mock instance. +func NewMockAccountKeeper(ctrl *gomock.Controller) *MockAccountKeeper { + mock := &MockAccountKeeper{ctrl: ctrl} + mock.recorder = &MockAccountKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { + return m.recorder +} + +// GetModuleAddress mocks base method. +func (m *MockAccountKeeper) GetModuleAddress(moduleName string) types.AccAddress { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetModuleAddress", moduleName) + ret0, _ := ret[0].(types.AccAddress) + return ret0 +} + +// GetModuleAddress indicates an expected call of GetModuleAddress. +func (mr *MockAccountKeeperMockRecorder) GetModuleAddress(moduleName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetModuleAddress", reflect.TypeOf((*MockAccountKeeper)(nil).GetModuleAddress), moduleName) +} + // MockBankKeeper is a mock of BankKeeper interface. type MockBankKeeper struct { ctrl *gomock.Controller diff --git a/x/fswap/types/genesis.go b/x/fswap/types/genesis.go index e12f0dfdf8..1ff0d61b89 100644 --- a/x/fswap/types/genesis.go +++ b/x/fswap/types/genesis.go @@ -29,22 +29,10 @@ func AuthorityCandidates() []sdk.AccAddress { // Validate performs basic genesis state validation returning an error upon any failure. func (gs *GenesisState) Validate() error { - for _, swap := range gs.GetSwaps() { - if err := swap.ValidateBasic(); err != nil { - return err - } - } - if err := gs.SwapStats.ValidateBasic(); err != nil { return err } - for _, swapped := range gs.GetSwappeds() { - if err := swapped.ValidateBasic(); err != nil { - return err - } - } - if len(gs.GetSwaps()) != len(gs.GetSwappeds()) { return ErrInvalidState.Wrap("number of swaps does not match number of Swappeds") } @@ -53,5 +41,24 @@ func (gs *GenesisState) Validate() error { return ErrInvalidState.Wrap("number of swaps does not match swap count in SwapStats") } + swappeds := gs.GetSwappeds() + for i, swap := range gs.GetSwaps() { + swapped := swappeds[i] + if err := swap.ValidateBasic(); err != nil { + return err + } + if err := swapped.ValidateBasic(); err != nil { + return err + } + if swap.FromDenom != swapped.FromCoinAmount.Denom { + return ErrInvalidState.Wrap("FromCoin in swap and swapped do not correspond") + } + if swap.ToDenom != swapped.ToCoinAmount.Denom { + return ErrInvalidState.Wrap("ToCoin in swap and swapped do not correspond") + } + if swap.AmountCapForToDenom.LT(swapped.ToCoinAmount.Amount) { + return ErrInvalidState.Wrap("AmountCapForToDenom cannot be exceeded") + } + } return nil } diff --git a/x/fswap/types/genesis_test.go b/x/fswap/types/genesis_test.go index 29308ea519..fc2ed9a781 100644 --- a/x/fswap/types/genesis_test.go +++ b/x/fswap/types/genesis_test.go @@ -5,14 +5,44 @@ import ( "github.com/stretchr/testify/require" + sdk "github.com/Finschia/finschia-sdk/types" "github.com/Finschia/finschia-sdk/x/fswap/types" ) -// todo: add tests 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 }{ { @@ -20,8 +50,121 @@ func TestGenesisStateValidate(t *testing.T) { 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)