github.com/Finschia/finschia-sdk@v0.49.1/x/fswap/keeper/keeper_test.go (about)

     1  package keeper_test
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/golang/mock/gomock"
     8  	"github.com/stretchr/testify/require"
     9  	"github.com/stretchr/testify/suite"
    10  	abci "github.com/tendermint/tendermint/abci/types"
    11  	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
    12  
    13  	"github.com/Finschia/finschia-sdk/crypto/keys/secp256k1"
    14  	"github.com/Finschia/finschia-sdk/simapp"
    15  	"github.com/Finschia/finschia-sdk/testutil/testdata"
    16  	sdk "github.com/Finschia/finschia-sdk/types"
    17  	sdkerrors "github.com/Finschia/finschia-sdk/types/errors"
    18  	authtypes "github.com/Finschia/finschia-sdk/x/auth/types"
    19  	bank "github.com/Finschia/finschia-sdk/x/bank/types"
    20  	"github.com/Finschia/finschia-sdk/x/foundation"
    21  	"github.com/Finschia/finschia-sdk/x/fswap/keeper"
    22  	"github.com/Finschia/finschia-sdk/x/fswap/testutil"
    23  	"github.com/Finschia/finschia-sdk/x/fswap/types"
    24  	govtypes "github.com/Finschia/finschia-sdk/x/gov/types"
    25  	minttypes "github.com/Finschia/finschia-sdk/x/mint/types"
    26  )
    27  
    28  type KeeperTestSuite struct {
    29  	suite.Suite
    30  
    31  	ctx         sdk.Context
    32  	goCtx       context.Context
    33  	keeper      keeper.Keeper
    34  	queryServer types.QueryServer
    35  	msgServer   types.MsgServer
    36  
    37  	accWithFromCoin sdk.AccAddress
    38  	accWithToCoin   sdk.AccAddress
    39  	initBalance     sdk.Int
    40  
    41  	swap            types.Swap
    42  	toDenomMetadata bank.Metadata
    43  }
    44  
    45  func (s *KeeperTestSuite) createRandomAccounts(n int) []sdk.AccAddress {
    46  	seenAddresses := make(map[string]bool, n)
    47  	addresses := make([]sdk.AccAddress, n)
    48  	for i := range addresses {
    49  		var address sdk.AccAddress
    50  		for {
    51  			pk := secp256k1.GenPrivKey().PubKey()
    52  			address = sdk.AccAddress(pk.Address())
    53  			if !seenAddresses[address.String()] {
    54  				seenAddresses[address.String()] = true
    55  				break
    56  			}
    57  		}
    58  		addresses[i] = address
    59  	}
    60  	return addresses
    61  }
    62  
    63  func (s *KeeperTestSuite) SetupTest() {
    64  	checkTx := false
    65  	app := simapp.Setup(checkTx)
    66  	testdata.RegisterInterfaces(app.InterfaceRegistry())
    67  	testdata.RegisterMsgServer(app.MsgServiceRouter(), testdata.MsgServerImpl{})
    68  	s.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{})
    69  	s.goCtx = sdk.WrapSDKContext(s.ctx)
    70  	s.keeper = app.FswapKeeper
    71  	s.queryServer = keeper.NewQueryServer(s.keeper)
    72  	s.msgServer = keeper.NewMsgServer(s.keeper)
    73  
    74  	numAcc := int64(2)
    75  	s.initBalance = sdk.NewInt(123456789)
    76  	pebSwapRateForCony, err := sdk.NewDecFromStr("148079656000000")
    77  	s.Require().NoError(err)
    78  	swapCap := sdk.NewIntFromBigInt(pebSwapRateForCony.Mul(s.initBalance.ToDec()).BigInt())
    79  	swapCap = swapCap.Mul(sdk.NewInt(numAcc))
    80  	s.Require().NoError(err)
    81  	s.swap = types.Swap{
    82  		FromDenom:           "fromdenom",
    83  		ToDenom:             "todenom",
    84  		AmountCapForToDenom: swapCap,
    85  		SwapRate:            pebSwapRateForCony,
    86  	}
    87  	s.toDenomMetadata = bank.Metadata{
    88  		Description: "This is metadata for to-coin",
    89  		DenomUnits: []*bank.DenomUnit{
    90  			{Denom: s.swap.ToDenom, Exponent: 0},
    91  		},
    92  		Base:    s.swap.ToDenom,
    93  		Display: s.swap.ToDenom,
    94  		Name:    "DUMMY",
    95  		Symbol:  "DUM",
    96  	}
    97  	err = s.toDenomMetadata.Validate()
    98  	s.Require().NoError(err)
    99  
   100  	fromDenom := bank.Metadata{
   101  		Description: "This is metadata for from-coin",
   102  		DenomUnits: []*bank.DenomUnit{
   103  			{Denom: s.swap.FromDenom, Exponent: 0},
   104  		},
   105  		Base:    s.swap.FromDenom,
   106  		Display: s.swap.FromDenom,
   107  		Name:    "FROM",
   108  		Symbol:  "FROM",
   109  	}
   110  	err = fromDenom.Validate()
   111  	s.Require().NoError(err)
   112  
   113  	app.BankKeeper.SetDenomMetaData(s.ctx, fromDenom)
   114  	s.createAccountsWithInitBalance(app)
   115  	app.AccountKeeper.GetModuleAccount(s.ctx, types.ModuleName)
   116  }
   117  
   118  func (s *KeeperTestSuite) createAccountsWithInitBalance(app *simapp.SimApp) {
   119  	addresses := []*sdk.AccAddress{
   120  		&s.accWithFromCoin,
   121  		&s.accWithToCoin,
   122  	}
   123  	for i, address := range s.createRandomAccounts(len(addresses)) {
   124  		*addresses[i] = address
   125  	}
   126  	minter := app.AccountKeeper.GetModuleAccount(s.ctx, minttypes.ModuleName).GetAddress()
   127  	fromAmount := sdk.NewCoins(sdk.NewCoin(s.swap.GetFromDenom(), s.initBalance))
   128  	s.Require().NoError(app.BankKeeper.MintCoins(s.ctx, minttypes.ModuleName, fromAmount))
   129  	s.Require().NoError(app.BankKeeper.SendCoins(s.ctx, minter, s.accWithFromCoin, fromAmount))
   130  
   131  	toAmount := sdk.NewCoins(sdk.NewCoin(s.swap.GetToDenom(), s.initBalance))
   132  	s.Require().NoError(app.BankKeeper.MintCoins(s.ctx, minttypes.ModuleName, toAmount))
   133  	s.Require().NoError(app.BankKeeper.SendCoins(s.ctx, minter, s.accWithToCoin, toAmount))
   134  }
   135  
   136  func TestKeeperTestSuite(t *testing.T) {
   137  	suite.Run(t, &KeeperTestSuite{})
   138  }
   139  
   140  func TestNewKeeper(t *testing.T) {
   141  	app := simapp.Setup(false)
   142  
   143  	ctrl := gomock.NewController(t)
   144  	defer ctrl.Finish()
   145  	authKeeper := testutil.NewMockAccountKeeper(ctrl)
   146  	testCases := map[string]struct {
   147  		malleate func()
   148  		isPanic  bool
   149  	}{
   150  		"fswap module account has not been set": {
   151  			malleate: func() {
   152  				authKeeper.EXPECT().GetModuleAddress(types.ModuleName).Return(nil).Times(1)
   153  				keeper.NewKeeper(app.AppCodec(), sdk.NewKVStoreKey(types.StoreKey), types.DefaultConfig(), types.DefaultAuthority().String(), authKeeper, app.BankKeeper)
   154  			},
   155  			isPanic: true,
   156  		},
   157  		"fswap authority must be the gov or foundation module account": {
   158  			malleate: func() {
   159  				authKeeper.EXPECT().GetModuleAddress(types.ModuleName).Return(authtypes.NewModuleAddress(types.ModuleName)).Times(1)
   160  				keeper.NewKeeper(app.AppCodec(), sdk.NewKVStoreKey(types.StoreKey), types.DefaultConfig(), authtypes.NewModuleAddress("invalid").String(), authKeeper, app.BankKeeper)
   161  			},
   162  			isPanic: true,
   163  		},
   164  		"success - gov authority": {
   165  			malleate: func() {
   166  				authKeeper.EXPECT().GetModuleAddress(types.ModuleName).Return(authtypes.NewModuleAddress(types.ModuleName)).Times(1)
   167  				keeper.NewKeeper(app.AppCodec(), sdk.NewKVStoreKey(types.StoreKey), types.DefaultConfig(), authtypes.NewModuleAddress(govtypes.ModuleName).String(), authKeeper, app.BankKeeper)
   168  			},
   169  			isPanic: false,
   170  		},
   171  		"success - foundation authority": {
   172  			malleate: func() {
   173  				authKeeper.EXPECT().GetModuleAddress(types.ModuleName).Return(authtypes.NewModuleAddress(types.ModuleName)).Times(1)
   174  				keeper.NewKeeper(app.AppCodec(), sdk.NewKVStoreKey(types.StoreKey), types.DefaultConfig(), authtypes.NewModuleAddress(foundation.ModuleName).String(), authKeeper, app.BankKeeper)
   175  			},
   176  			isPanic: false,
   177  		},
   178  	}
   179  	for name, tc := range testCases {
   180  		t.Run(name, func(t *testing.T) {
   181  			if tc.isPanic {
   182  				require.Panics(t, tc.malleate)
   183  			} else {
   184  				tc.malleate()
   185  			}
   186  		})
   187  	}
   188  }
   189  
   190  func (s *KeeperTestSuite) TestSwap() {
   191  	swap2ExpectedAmount, ok := sdk.NewIntFromString("296159312000000")
   192  	s.Require().True(ok)
   193  	swap100ExpectedAmount, ok := sdk.NewIntFromString("14807965600000000")
   194  	s.Require().True(ok)
   195  	swapAllExpectedBalance, ok := sdk.NewIntFromString("18281438845984584000000")
   196  	s.Require().True(ok)
   197  	testCases := map[string]struct {
   198  		from             sdk.AccAddress
   199  		amountToSwap     sdk.Coin
   200  		toDenom          string
   201  		expectedAmount   sdk.Int
   202  		shouldThrowError bool
   203  		expectedError    error
   204  	}{
   205  		"swap 2 from-denom": {
   206  			s.accWithFromCoin,
   207  			sdk.NewCoin(s.swap.GetFromDenom(), sdk.NewInt(2)),
   208  			s.swap.GetToDenom(),
   209  			swap2ExpectedAmount,
   210  			false,
   211  			nil,
   212  		},
   213  		"swap some": {
   214  			s.accWithFromCoin,
   215  			sdk.NewCoin(s.swap.GetFromDenom(), sdk.NewInt(100)),
   216  			s.swap.GetToDenom(),
   217  			swap100ExpectedAmount,
   218  			false,
   219  			nil,
   220  		},
   221  		"swap all the balance": {
   222  			s.accWithFromCoin,
   223  			sdk.NewCoin(s.swap.GetFromDenom(), s.initBalance),
   224  			s.swap.GetToDenom(),
   225  			swapAllExpectedBalance,
   226  			false,
   227  			nil,
   228  		},
   229  		"swap without holding enough balance": {
   230  			s.accWithFromCoin,
   231  			sdk.NewCoin(s.swap.GetFromDenom(), sdk.OneInt().Add(s.initBalance)),
   232  			s.swap.GetToDenom(),
   233  			sdk.ZeroInt(),
   234  			true,
   235  			sdkerrors.ErrInsufficientFunds,
   236  		},
   237  		"account holding new coin only": {
   238  			s.accWithToCoin,
   239  			sdk.NewCoin(s.swap.GetFromDenom(), sdk.NewInt(100)),
   240  			s.swap.GetToDenom(),
   241  			sdk.ZeroInt(),
   242  			true,
   243  			sdkerrors.ErrInsufficientFunds,
   244  		},
   245  		"invalid: unregistered swap": {
   246  			s.accWithToCoin,
   247  			sdk.NewCoin(s.swap.GetFromDenom(), sdk.NewInt(100)),
   248  			"nono",
   249  			sdk.ZeroInt(),
   250  			true,
   251  			sdkerrors.ErrNotFound,
   252  		},
   253  		"invalid: amount exceed": {
   254  			s.accWithToCoin,
   255  			sdk.NewCoin(s.swap.GetFromDenom(), s.swap.AmountCapForToDenom.Add(sdk.OneInt())),
   256  			s.swap.GetToDenom(),
   257  			sdk.ZeroInt(),
   258  			true,
   259  			types.ErrExceedSwappableToCoinAmount,
   260  		},
   261  	}
   262  	for name, tc := range testCases {
   263  		s.Run(name, func() {
   264  			ctx, _ := s.ctx.CacheContext()
   265  			err := s.keeper.SetSwap(ctx, s.swap, s.toDenomMetadata)
   266  			s.Require().NoError(err)
   267  
   268  			err = s.keeper.Swap(ctx, tc.from, tc.amountToSwap, tc.toDenom)
   269  			if tc.shouldThrowError {
   270  				s.Require().ErrorIs(err, tc.expectedError)
   271  				return
   272  			}
   273  			s.Require().NoError(err)
   274  
   275  			actualAmount := s.keeper.GetBalance(ctx, tc.from, s.swap.GetToDenom()).Amount
   276  			s.Require().Equal(tc.expectedAmount, actualAmount)
   277  		})
   278  	}
   279  }
   280  
   281  func (s *KeeperTestSuite) TestSetSwap() {
   282  	ctrl := gomock.NewController(s.T())
   283  	defer ctrl.Finish()
   284  	bankKeeper := testutil.NewMockBankKeeper(ctrl)
   285  
   286  	testCases := map[string]struct {
   287  		swap           types.Swap
   288  		toDenomMeta    bank.Metadata
   289  		malleate       func()
   290  		expectedError  error
   291  		expectedEvents sdk.Events
   292  	}{
   293  		"valid": {
   294  			types.Swap{
   295  				FromDenom:           "fromdenom",
   296  				ToDenom:             "todenom",
   297  				AmountCapForToDenom: sdk.OneInt(),
   298  				SwapRate:            sdk.OneDec(),
   299  			},
   300  			s.toDenomMetadata,
   301  			func() {
   302  				bankKeeper.EXPECT().GetDenomMetaData(gomock.Any(), "fromdenom").Return(bank.Metadata{}, true).Times(1)
   303  				bankKeeper.EXPECT().GetDenomMetaData(gomock.Any(), "todenom").Return(bank.Metadata{}, false).Times(1)
   304  				bankKeeper.EXPECT().SetDenomMetaData(gomock.Any(), s.toDenomMetadata).Times(1)
   305  			},
   306  			nil,
   307  			sdk.Events{
   308  				sdk.Event{
   309  					Type: "lbm.fswap.v1.EventSetSwap",
   310  					Attributes: []abci.EventAttribute{
   311  						{
   312  							Key:   []byte("swap"),
   313  							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},
   314  							Index: false,
   315  						},
   316  					},
   317  				},
   318  				sdk.Event{
   319  					Type: "lbm.fswap.v1.EventAddDenomMetadata",
   320  					Attributes: []abci.EventAttribute{
   321  						{
   322  							Key:   []byte("metadata"),
   323  							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},
   324  							Index: false,
   325  						},
   326  					},
   327  				},
   328  			},
   329  		},
   330  		"to-denom metadata has been stored": {
   331  			types.Swap{
   332  				FromDenom:           "fromdenom",
   333  				ToDenom:             "todenom",
   334  				AmountCapForToDenom: sdk.OneInt(),
   335  				SwapRate:            sdk.OneDec(),
   336  			},
   337  			s.toDenomMetadata,
   338  			func() {
   339  				bankKeeper.EXPECT().GetDenomMetaData(gomock.Any(), "fromdenom").Return(bank.Metadata{}, true).Times(1)
   340  				bankKeeper.EXPECT().GetDenomMetaData(gomock.Any(), "todenom").Return(s.toDenomMetadata, true).Times(1)
   341  			},
   342  			nil,
   343  			sdk.Events{
   344  				sdk.Event{
   345  					Type: "lbm.fswap.v1.EventSetSwap",
   346  					Attributes: []abci.EventAttribute{
   347  						{
   348  							Key:   []byte("swap"),
   349  							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},
   350  							Index: false,
   351  						},
   352  					},
   353  				},
   354  			},
   355  		},
   356  		"from-denom does not exist": {
   357  			types.Swap{
   358  				FromDenom:           "fakedenom",
   359  				ToDenom:             "todenom",
   360  				AmountCapForToDenom: sdk.OneInt(),
   361  				SwapRate:            sdk.OneDec(),
   362  			},
   363  			s.toDenomMetadata,
   364  			func() {
   365  				bankKeeper.EXPECT().GetDenomMetaData(gomock.Any(), "fakedenom").Return(bank.Metadata{}, false).Times(1)
   366  			},
   367  			sdkerrors.ErrInvalidRequest,
   368  			sdk.Events{},
   369  		},
   370  		"to-denom metadata change not allowed": {
   371  			types.Swap{
   372  				FromDenom:           "fromdenom",
   373  				ToDenom:             "change",
   374  				AmountCapForToDenom: sdk.OneInt(),
   375  				SwapRate:            sdk.OneDec(),
   376  			},
   377  			bank.Metadata{
   378  				Description: s.toDenomMetadata.Description,
   379  				DenomUnits:  s.toDenomMetadata.DenomUnits,
   380  				Base:        "change",
   381  				Display:     "change",
   382  				Name:        s.toDenomMetadata.Name,
   383  				Symbol:      s.toDenomMetadata.Symbol,
   384  			},
   385  			func() {
   386  				bankKeeper.EXPECT().GetDenomMetaData(gomock.Any(), "fromdenom").Return(bank.Metadata{}, true).Times(1)
   387  				bankKeeper.EXPECT().GetDenomMetaData(gomock.Any(), "change").Return(s.toDenomMetadata, true).Times(1)
   388  			},
   389  			sdkerrors.ErrInvalidRequest,
   390  			sdk.Events{},
   391  		},
   392  	}
   393  	for name, tc := range testCases {
   394  		s.Run(name, func() {
   395  			ctx, _ := s.ctx.CacheContext()
   396  			tc.malleate()
   397  			s.keeper.BankKeeper = bankKeeper
   398  
   399  			err := s.keeper.SetSwap(ctx, tc.swap, tc.toDenomMeta)
   400  			if tc.expectedError != nil {
   401  				s.Require().ErrorIs(err, tc.expectedError)
   402  				return
   403  			}
   404  			events := ctx.EventManager().Events()
   405  			s.Require().Equal(tc.expectedEvents, events)
   406  		})
   407  	}
   408  }
   409  
   410  func (s *KeeperTestSuite) TestSwapValidateBasic() {
   411  	testCases := map[string]struct {
   412  		swap             types.Swap
   413  		shouldThrowError bool
   414  		expectedError    error
   415  	}{
   416  		"valid": {
   417  			types.Swap{
   418  				FromDenom:           "fromD",
   419  				ToDenom:             "toD",
   420  				AmountCapForToDenom: sdk.OneInt(),
   421  				SwapRate:            sdk.OneDec(),
   422  			},
   423  			false,
   424  			nil,
   425  		},
   426  		"invalid empty from-denom": {
   427  			types.Swap{
   428  				FromDenom:           "",
   429  				ToDenom:             "toD",
   430  				AmountCapForToDenom: sdk.OneInt(),
   431  				SwapRate:            sdk.OneDec(),
   432  			},
   433  			true,
   434  			sdkerrors.ErrInvalidRequest,
   435  		},
   436  		"invalid empty to-denom": {
   437  			types.Swap{
   438  				FromDenom:           "fromD",
   439  				ToDenom:             "",
   440  				AmountCapForToDenom: sdk.OneInt(),
   441  				SwapRate:            sdk.OneDec(),
   442  			},
   443  			true,
   444  			sdkerrors.ErrInvalidRequest,
   445  		},
   446  		"invalid zero amount cap for to-denom": {
   447  			types.Swap{
   448  				FromDenom:           "fromD",
   449  				ToDenom:             "toD",
   450  				AmountCapForToDenom: sdk.ZeroInt(),
   451  				SwapRate:            sdk.OneDec(),
   452  			},
   453  			true,
   454  			sdkerrors.ErrInvalidRequest,
   455  		},
   456  		"invalid zero swap-rate": {
   457  			types.Swap{
   458  				FromDenom:           "fromD",
   459  				ToDenom:             "toD",
   460  				AmountCapForToDenom: sdk.OneInt(),
   461  				SwapRate:            sdk.ZeroDec(),
   462  			},
   463  			true,
   464  			sdkerrors.ErrInvalidRequest,
   465  		},
   466  		"invalid the same from-denom and to-denom": {
   467  			types.Swap{
   468  				FromDenom:           "same",
   469  				ToDenom:             "same",
   470  				AmountCapForToDenom: sdk.OneInt(),
   471  				SwapRate:            sdk.OneDec(),
   472  			},
   473  			true,
   474  			sdkerrors.ErrInvalidRequest,
   475  		},
   476  	}
   477  	for name, tc := range testCases {
   478  		s.Run(name, func() {
   479  			err := tc.swap.ValidateBasic()
   480  			if tc.shouldThrowError {
   481  				s.Require().ErrorIs(err, tc.expectedError)
   482  				return
   483  			}
   484  			s.Require().NoError(err)
   485  		})
   486  	}
   487  }