github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/ibc-go/modules/apps/29-fee/ibc_middleware_test.go (about)

     1  package fee_test
     2  
     3  import (
     4  	"fmt"
     5  
     6  	ibctesting "github.com/fibonacci-chain/fbc/libs/ibc-go/testing"
     7  
     8  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
     9  	transfertypes "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/apps/transfer/types"
    10  	channeltypes "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/04-channel/types"
    11  
    12  	capabilitytypes "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/capability/types"
    13  	fee "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/apps/29-fee"
    14  	"github.com/fibonacci-chain/fbc/libs/ibc-go/modules/apps/29-fee/types"
    15  	host "github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/24-host"
    16  	"github.com/fibonacci-chain/fbc/libs/ibc-go/modules/core/exported"
    17  	ibcmock "github.com/fibonacci-chain/fbc/libs/ibc-go/testing/mock"
    18  )
    19  
    20  var (
    21  	defaultRecvFee    = sdk.CoinAdapters{sdk.CoinAdapter{Denom: sdk.DefaultIbcWei, Amount: sdk.NewInt(100)}}
    22  	defaultAckFee     = sdk.CoinAdapters{sdk.CoinAdapter{Denom: sdk.DefaultIbcWei, Amount: sdk.NewInt(200)}}
    23  	defaultTimeoutFee = sdk.CoinAdapters{sdk.CoinAdapter{Denom: sdk.DefaultIbcWei, Amount: sdk.NewInt(300)}}
    24  	smallAmount       = sdk.CoinAdapters{sdk.CoinAdapter{Denom: sdk.DefaultIbcWei, Amount: sdk.NewInt(50)}}
    25  )
    26  
    27  // Tests OnChanOpenInit on ChainA
    28  func (suite *FeeTestSuite) TestOnChanOpenInit() {
    29  	testCases := []struct {
    30  		name         string
    31  		version      string
    32  		expPass      bool
    33  		isFeeEnabled bool
    34  	}{
    35  		{
    36  			"success - valid fee middleware and mock version",
    37  			string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{FeeVersion: types.Version, AppVersion: ibcmock.Version})),
    38  			true,
    39  			true,
    40  		},
    41  		{
    42  			"success - fee version not included, only perform mock logic",
    43  			ibcmock.Version,
    44  			true,
    45  			false,
    46  		},
    47  		{
    48  			"invalid fee middleware version",
    49  			string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{FeeVersion: "invalid-ics29-1", AppVersion: ibcmock.Version})),
    50  			false,
    51  			false,
    52  		},
    53  		{
    54  			"invalid mock version",
    55  			string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{FeeVersion: types.Version, AppVersion: "invalid-mock-version"})),
    56  			false,
    57  			false,
    58  		},
    59  		{
    60  			"mock version not wrapped",
    61  			types.Version,
    62  			false,
    63  			false,
    64  		},
    65  		{
    66  			"passing an empty string returns default version",
    67  			"",
    68  			true,
    69  			true,
    70  		},
    71  	}
    72  
    73  	for _, tc := range testCases {
    74  		tc := tc
    75  
    76  		suite.Run(tc.name, func() {
    77  			// reset suite
    78  			suite.SetupTest()
    79  			suite.coordinator.SetupConnections(suite.path)
    80  
    81  			// setup mock callback
    82  			suite.chainA.GetSimApp().FeeMockModule.IBCApp.OnChanOpenInit = func(ctx sdk.Context, order channeltypes.Order, connectionHops []string,
    83  				portID, channelID string, chanCap *capabilitytypes.Capability,
    84  				counterparty channeltypes.Counterparty, version string,
    85  			) (string, error) {
    86  				if version != ibcmock.Version {
    87  					return "", fmt.Errorf("incorrect mock version")
    88  				}
    89  				return ibcmock.Version, nil
    90  			}
    91  
    92  			suite.path.EndpointA.ChannelID = ibctesting.FirstChannelID
    93  
    94  			counterparty := channeltypes.NewCounterparty(suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID)
    95  			channel := &channeltypes.Channel{
    96  				State:          channeltypes.INIT,
    97  				Ordering:       channeltypes.UNORDERED,
    98  				Counterparty:   counterparty,
    99  				ConnectionHops: []string{suite.path.EndpointA.ConnectionID},
   100  				Version:        tc.version,
   101  			}
   102  
   103  			module, _, err := suite.chainA.GetSimApp().GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), ibctesting.MockFeePort)
   104  			suite.Require().NoError(err)
   105  
   106  			chanCap, err := suite.chainA.GetSimApp().GetScopedIBCKeeper().NewCapability(suite.chainA.GetContext(), host.ChannelCapabilityPath(suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID))
   107  			suite.Require().NoError(err)
   108  
   109  			cbs, ok := suite.chainA.GetSimApp().GetIBCKeeper().Router.GetRoute(module)
   110  			suite.Require().True(ok)
   111  
   112  			version, err := cbs.OnChanOpenInit(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(),
   113  				suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, chanCap, counterparty, channel.Version)
   114  
   115  			if tc.expPass {
   116  				// check if the channel is fee enabled. If so version string should include metaData
   117  				if tc.isFeeEnabled {
   118  					versionMetadata := types.Metadata{
   119  						FeeVersion: types.Version,
   120  						AppVersion: ibcmock.Version,
   121  					}
   122  
   123  					versionBytes, err := types.ModuleCdc.MarshalJSON(&versionMetadata)
   124  					suite.Require().NoError(err)
   125  
   126  					suite.Require().Equal(version, string(versionBytes))
   127  				} else {
   128  					suite.Require().Equal(ibcmock.Version, version)
   129  				}
   130  
   131  				suite.Require().NoError(err, "unexpected error from version: %s", tc.version)
   132  			} else {
   133  				suite.Require().Error(err, "error not returned for version: %s", tc.version)
   134  				suite.Require().Equal("", version)
   135  			}
   136  		})
   137  	}
   138  }
   139  
   140  // Tests OnChanOpenTry on ChainA
   141  func (suite *FeeTestSuite) TestOnChanOpenTry() {
   142  	testCases := []struct {
   143  		name      string
   144  		cpVersion string
   145  		expPass   bool
   146  	}{
   147  		{
   148  			"success - valid fee middleware version",
   149  			string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{FeeVersion: types.Version, AppVersion: ibcmock.Version})),
   150  			true,
   151  		},
   152  		{
   153  			"success - valid mock version",
   154  			ibcmock.Version,
   155  			true,
   156  		},
   157  		{
   158  			"invalid fee middleware version",
   159  			string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{FeeVersion: "invalid-ics29-1", AppVersion: ibcmock.Version})),
   160  			false,
   161  		},
   162  		{
   163  			"invalid mock version",
   164  			string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{FeeVersion: types.Version, AppVersion: "invalid-mock-version"})),
   165  			false,
   166  		},
   167  	}
   168  
   169  	for _, tc := range testCases {
   170  		tc := tc
   171  
   172  		suite.Run(tc.name, func() {
   173  			// reset suite
   174  			suite.SetupTest()
   175  			suite.coordinator.SetupConnections(suite.path)
   176  			suite.path.EndpointB.ChanOpenInit()
   177  
   178  			// setup mock callback
   179  			suite.chainA.GetSimApp().FeeMockModule.IBCApp.OnChanOpenTry = func(ctx sdk.Context, order channeltypes.Order, connectionHops []string,
   180  				portID, channelID string, chanCap *capabilitytypes.Capability,
   181  				counterparty channeltypes.Counterparty, counterpartyVersion string,
   182  			) (string, error) {
   183  				if counterpartyVersion != ibcmock.Version {
   184  					return "", fmt.Errorf("incorrect mock version")
   185  				}
   186  				return ibcmock.Version, nil
   187  			}
   188  
   189  			var (
   190  				chanCap *capabilitytypes.Capability
   191  				ok      bool
   192  				err     error
   193  			)
   194  
   195  			chanCap, err = suite.chainA.GetSimApp().GetScopedIBCKeeper().NewCapability(suite.chainA.GetContext(), host.ChannelCapabilityPath(suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID))
   196  			suite.Require().NoError(err)
   197  
   198  			suite.path.EndpointA.ChannelID = ibctesting.FirstChannelID
   199  
   200  			counterparty := channeltypes.NewCounterparty(suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID)
   201  			channel := &channeltypes.Channel{
   202  				State:          channeltypes.INIT,
   203  				Ordering:       channeltypes.UNORDERED,
   204  				Counterparty:   counterparty,
   205  				ConnectionHops: []string{suite.path.EndpointA.ConnectionID},
   206  				Version:        tc.cpVersion,
   207  			}
   208  
   209  			module, _, err := suite.chainA.GetSimApp().GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), ibctesting.MockFeePort)
   210  			suite.Require().NoError(err)
   211  
   212  			cbs, ok := suite.chainA.GetSimApp().GetIBCKeeper().Router.GetRoute(module)
   213  			suite.Require().True(ok)
   214  
   215  			_, err = cbs.OnChanOpenTry(suite.chainA.GetContext(), channel.Ordering, channel.GetConnectionHops(),
   216  				suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, chanCap,
   217  				counterparty, suite.path.EndpointA.ChannelConfig.Version, tc.cpVersion)
   218  
   219  			if tc.expPass {
   220  				suite.Require().NoError(err)
   221  			} else {
   222  				suite.Require().Error(err)
   223  			}
   224  		})
   225  	}
   226  }
   227  
   228  // Tests OnChanOpenAck on ChainA
   229  func (suite *FeeTestSuite) TestOnChanOpenAck() {
   230  	testCases := []struct {
   231  		name      string
   232  		cpVersion string
   233  		malleate  func(suite *FeeTestSuite)
   234  		expPass   bool
   235  	}{
   236  		{
   237  			"success",
   238  			string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{FeeVersion: types.Version, AppVersion: ibcmock.Version})),
   239  			func(suite *FeeTestSuite) {},
   240  			true,
   241  		},
   242  		{
   243  			"invalid fee version",
   244  			string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{FeeVersion: "invalid-ics29-1", AppVersion: ibcmock.Version})),
   245  			func(suite *FeeTestSuite) {},
   246  			false,
   247  		},
   248  		{
   249  			"invalid mock version",
   250  			string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{FeeVersion: types.Version, AppVersion: "invalid-mock-version"})),
   251  			func(suite *FeeTestSuite) {},
   252  			false,
   253  		},
   254  		{
   255  			"invalid version fails to unmarshal metadata",
   256  			"invalid-version",
   257  			func(suite *FeeTestSuite) {},
   258  			false,
   259  		},
   260  		{
   261  			"previous INIT set without fee, however counterparty set fee version", // note this can only happen with incompetent or malicious counterparty chain
   262  			string(types.ModuleCdc.MustMarshalJSON(&types.Metadata{FeeVersion: types.Version, AppVersion: ibcmock.Version})),
   263  			func(suite *FeeTestSuite) {
   264  				// do the first steps without fee version, then pass the fee version as counterparty version in ChanOpenACK
   265  				suite.path.EndpointA.ChannelConfig.Version = ibcmock.Version
   266  				suite.path.EndpointB.ChannelConfig.Version = ibcmock.Version
   267  			},
   268  			false,
   269  		},
   270  	}
   271  
   272  	for _, tc := range testCases {
   273  		tc := tc
   274  		suite.Run(tc.name, func() {
   275  			suite.SetupTest()
   276  			suite.coordinator.SetupConnections(suite.path)
   277  
   278  			// setup mock callback
   279  			suite.chainA.GetSimApp().FeeMockModule.IBCApp.OnChanOpenAck = func(
   280  				ctx sdk.Context, portID, channelID string, counterpartyChannelID string, counterpartyVersion string,
   281  			) error {
   282  				if counterpartyVersion != ibcmock.Version {
   283  					return fmt.Errorf("incorrect mock version")
   284  				}
   285  				return nil
   286  			}
   287  
   288  			// malleate test case
   289  			tc.malleate(suite)
   290  
   291  			suite.path.EndpointA.ChanOpenInit()
   292  			suite.path.EndpointB.ChanOpenTry()
   293  
   294  			module, _, err := suite.chainA.GetSimApp().GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), ibctesting.MockFeePort)
   295  			suite.Require().NoError(err)
   296  
   297  			cbs, ok := suite.chainA.GetSimApp().GetIBCKeeper().Router.GetRoute(module)
   298  			suite.Require().True(ok)
   299  
   300  			err = cbs.OnChanOpenAck(suite.chainA.GetContext(), suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, suite.path.EndpointA.Counterparty.ChannelID, tc.cpVersion)
   301  			if tc.expPass {
   302  				suite.Require().NoError(err, "unexpected error for case: %s", tc.name)
   303  			} else {
   304  				suite.Require().Error(err, "%s expected error but returned none", tc.name)
   305  			}
   306  		})
   307  	}
   308  }
   309  
   310  func (suite *FeeTestSuite) TestOnChanCloseInit() {
   311  	var (
   312  		refundAcc sdk.AccAddress
   313  		fee       types.Fee
   314  	)
   315  
   316  	testCases := []struct {
   317  		name     string
   318  		malleate func()
   319  		expPass  bool
   320  	}{
   321  		{
   322  			"success", func() {}, true,
   323  		},
   324  		{
   325  			"application callback fails", func() {
   326  				suite.chainA.GetSimApp().FeeMockModule.IBCApp.OnChanCloseInit = func(
   327  					ctx sdk.Context, portID, channelID string,
   328  				) error {
   329  					return fmt.Errorf("application callback fails")
   330  				}
   331  			}, false,
   332  		},
   333  		{
   334  			"RefundFeesOnChannelClosure continues - invalid refund address", func() {
   335  				// store the fee in state & update escrow account balance
   336  				packetID := channeltypes.NewPacketId(suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, uint64(1))
   337  				packetFees := types.NewPacketFees([]types.PacketFee{types.NewPacketFee(fee, "invalid refund address", nil)})
   338  
   339  				suite.chainA.GetSimApp().IBCFeeKeeper.SetFeesInEscrow(suite.chainA.GetContext(), packetID, packetFees)
   340  				err := suite.chainA.GetSimApp().SupplyKeeper.SendCoinsFromAccountToModule(suite.chainA.GetContext(), refundAcc, types.ModuleName, fee.Total().ToCoins())
   341  				suite.Require().NoError(err)
   342  			},
   343  			true,
   344  		},
   345  		{
   346  			"fee module locked", func() {
   347  				lockFeeModule(suite.chainA)
   348  			},
   349  			false,
   350  		},
   351  		{
   352  			"fee module is not enabled", func() {
   353  				suite.chainA.GetSimApp().IBCFeeKeeper.DeleteFeeEnabled(suite.chainA.GetContext(), suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID)
   354  			},
   355  			true,
   356  		},
   357  	}
   358  
   359  	for _, tc := range testCases {
   360  		tc := tc
   361  		suite.Run(tc.name, func() {
   362  			suite.SetupTest()
   363  			suite.coordinator.Setup(suite.path) // setup channel
   364  
   365  			packetID := channeltypes.NewPacketId(suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, 1)
   366  			fee = types.Fee{
   367  				RecvFee:    defaultRecvFee,
   368  				AckFee:     defaultAckFee,
   369  				TimeoutFee: defaultTimeoutFee,
   370  			}
   371  
   372  			refundAcc = suite.chainA.SenderAccount().GetAddress()
   373  			packetFee := types.NewPacketFee(fee, refundAcc.String(), []string{})
   374  
   375  			suite.chainA.GetSimApp().IBCFeeKeeper.SetFeesInEscrow(suite.chainA.GetContext(), packetID, types.NewPacketFees([]types.PacketFee{packetFee}))
   376  			err := suite.chainA.GetSimApp().SupplyKeeper.SendCoinsFromAccountToModule(suite.chainA.GetContext(), refundAcc, types.ModuleName, fee.Total().ToCoins())
   377  			suite.Require().NoError(err)
   378  
   379  			tc.malleate()
   380  
   381  			module, _, err := suite.chainA.GetSimApp().GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), ibctesting.MockFeePort)
   382  			suite.Require().NoError(err)
   383  
   384  			cbs, ok := suite.chainA.GetSimApp().GetIBCKeeper().Router.GetRoute(module)
   385  			suite.Require().True(ok)
   386  
   387  			err = cbs.OnChanCloseInit(suite.chainA.GetContext(), suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID)
   388  
   389  			if tc.expPass {
   390  				suite.Require().NoError(err)
   391  			} else {
   392  				suite.Require().Error(err)
   393  			}
   394  		})
   395  	}
   396  }
   397  
   398  // Tests OnChanCloseConfirm on chainA
   399  func (suite *FeeTestSuite) TestOnChanCloseConfirm() {
   400  	var (
   401  		refundAcc sdk.AccAddress
   402  		fee       types.Fee
   403  	)
   404  
   405  	testCases := []struct {
   406  		name     string
   407  		malleate func()
   408  		expPass  bool
   409  	}{
   410  		{
   411  			"success", func() {}, true,
   412  		},
   413  		{
   414  			"application callback fails", func() {
   415  				suite.chainA.GetSimApp().FeeMockModule.IBCApp.OnChanCloseConfirm = func(
   416  					ctx sdk.Context, portID, channelID string,
   417  				) error {
   418  					return fmt.Errorf("application callback fails")
   419  				}
   420  			}, false,
   421  		},
   422  		{
   423  			"RefundChannelFeesOnClosure continues - refund address is invalid", func() {
   424  				// store the fee in state & update escrow account balance
   425  				packetID := channeltypes.NewPacketId(suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, uint64(1))
   426  				packetFees := types.NewPacketFees([]types.PacketFee{types.NewPacketFee(fee, "invalid refund address", nil)})
   427  
   428  				suite.chainA.GetSimApp().IBCFeeKeeper.SetFeesInEscrow(suite.chainA.GetContext(), packetID, packetFees)
   429  				err := suite.chainA.GetSimApp().SupplyKeeper.SendCoinsFromAccountToModule(suite.chainA.GetContext(), refundAcc, types.ModuleName, fee.Total().ToCoins())
   430  				suite.Require().NoError(err)
   431  			},
   432  			true,
   433  		},
   434  		{
   435  			"fee module locked", func() {
   436  				lockFeeModule(suite.chainA)
   437  			},
   438  			false,
   439  		},
   440  		{
   441  			"fee module is not enabled", func() {
   442  				suite.chainA.GetSimApp().IBCFeeKeeper.DeleteFeeEnabled(suite.chainA.GetContext(), suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID)
   443  			},
   444  			true,
   445  		},
   446  	}
   447  
   448  	for _, tc := range testCases {
   449  		tc := tc
   450  
   451  		suite.Run(tc.name, func() {
   452  			suite.SetupTest()
   453  			suite.coordinator.Setup(suite.path) // setup channel
   454  
   455  			packetID := channeltypes.NewPacketId(suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, 1)
   456  			fee = types.Fee{
   457  				RecvFee:    defaultRecvFee,
   458  				AckFee:     defaultAckFee,
   459  				TimeoutFee: defaultTimeoutFee,
   460  			}
   461  
   462  			refundAcc = suite.chainA.SenderAccount().GetAddress()
   463  			packetFee := types.NewPacketFee(fee, refundAcc.String(), []string{})
   464  
   465  			suite.chainA.GetSimApp().IBCFeeKeeper.SetFeesInEscrow(suite.chainA.GetContext(), packetID, types.NewPacketFees([]types.PacketFee{packetFee}))
   466  			err := suite.chainA.GetSimApp().SupplyKeeper.SendCoinsFromAccountToModule(suite.chainA.GetContext(), refundAcc, types.ModuleName, fee.Total().ToCoins())
   467  			suite.Require().NoError(err)
   468  
   469  			tc.malleate()
   470  
   471  			module, _, err := suite.chainA.GetSimApp().GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), ibctesting.MockFeePort)
   472  			suite.Require().NoError(err)
   473  
   474  			cbs, ok := suite.chainA.GetSimApp().GetIBCKeeper().Router.GetRoute(module)
   475  			suite.Require().True(ok)
   476  
   477  			err = cbs.OnChanCloseConfirm(suite.chainA.GetContext(), suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID)
   478  
   479  			if tc.expPass {
   480  				suite.Require().NoError(err)
   481  			} else {
   482  				suite.Require().Error(err)
   483  			}
   484  		})
   485  	}
   486  }
   487  
   488  func (suite *FeeTestSuite) TestOnRecvPacket() {
   489  	testCases := []struct {
   490  		name     string
   491  		malleate func()
   492  		// forwardRelayer bool indicates if there is a forwardRelayer address set
   493  		forwardRelayer bool
   494  		feeEnabled     bool
   495  	}{
   496  		{
   497  			"success",
   498  			func() {},
   499  			true,
   500  			true,
   501  		},
   502  		{
   503  			"async write acknowledgement: ack is nil",
   504  			func() {
   505  				// setup mock callback
   506  				suite.chainB.GetSimApp().FeeMockModule.IBCApp.OnRecvPacket = func(
   507  					ctx sdk.Context,
   508  					packet channeltypes.Packet,
   509  					relayer sdk.AccAddress,
   510  				) exported.Acknowledgement {
   511  					return nil
   512  				}
   513  			},
   514  			true,
   515  			true,
   516  		},
   517  		{
   518  			"fee not enabled",
   519  			func() {
   520  				suite.chainB.GetSimApp().IBCFeeKeeper.DeleteFeeEnabled(suite.chainB.GetContext(), suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID)
   521  			},
   522  			true,
   523  			false,
   524  		},
   525  		{
   526  			"forward address is not found",
   527  			func() {
   528  				suite.chainB.GetSimApp().IBCFeeKeeper.SetCounterpartyPayeeAddress(suite.chainB.GetContext(), suite.chainA.SenderAccount().GetAddress().String(), "", suite.path.EndpointB.ChannelID)
   529  			},
   530  			false,
   531  			true,
   532  		},
   533  	}
   534  
   535  	for _, tc := range testCases {
   536  		tc := tc
   537  		suite.Run(tc.name, func() {
   538  			suite.SetupTest()
   539  			// setup pathAToC (chainA -> chainC) first in order to have different channel IDs for chainA & chainB
   540  			suite.coordinator.Setup(suite.pathAToC)
   541  			// setup path for chainA -> chainB
   542  			suite.coordinator.Setup(suite.path)
   543  
   544  			suite.chainB.GetSimApp().IBCFeeKeeper.SetFeeEnabled(suite.chainB.GetContext(), suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID)
   545  
   546  			packet := suite.CreateMockPacket()
   547  
   548  			// set up module and callbacks
   549  			module, _, err := suite.chainB.GetSimApp().GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainB.GetContext(), ibctesting.MockFeePort)
   550  			suite.Require().NoError(err)
   551  
   552  			cbs, ok := suite.chainB.GetSimApp().GetIBCKeeper().Router.GetRoute(module)
   553  			suite.Require().True(ok)
   554  
   555  			suite.chainB.GetSimApp().IBCFeeKeeper.SetCounterpartyPayeeAddress(suite.chainB.GetContext(), suite.chainA.SenderAccount().GetAddress().String(), suite.chainB.SenderAccount().GetAddress().String(), suite.path.EndpointB.ChannelID)
   556  
   557  			// malleate test case
   558  			tc.malleate()
   559  
   560  			result := cbs.OnRecvPacket(suite.chainB.GetContext(), packet, suite.chainA.SenderAccount().GetAddress())
   561  
   562  			switch {
   563  			case tc.name == "success":
   564  				forwardAddr, _ := suite.chainB.GetSimApp().IBCFeeKeeper.GetCounterpartyPayeeAddress(suite.chainB.GetContext(), suite.chainA.SenderAccount().GetAddress().String(), suite.path.EndpointB.ChannelID)
   565  
   566  				expectedAck := types.IncentivizedAcknowledgement{
   567  					AppAcknowledgement:    ibcmock.MockAcknowledgement.Acknowledgement(),
   568  					ForwardRelayerAddress: forwardAddr,
   569  					UnderlyingAppSuccess:  true,
   570  				}
   571  				suite.Require().Equal(expectedAck, result)
   572  
   573  			case !tc.feeEnabled:
   574  				suite.Require().Equal(ibcmock.MockAcknowledgement, result)
   575  
   576  			case tc.forwardRelayer && result == nil:
   577  				suite.Require().Equal(nil, result)
   578  				packetID := channeltypes.NewPacketId(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence())
   579  
   580  				// retrieve the forward relayer that was stored in `onRecvPacket`
   581  				relayer, _ := suite.chainB.GetSimApp().IBCFeeKeeper.GetRelayerAddressForAsyncAck(suite.chainB.GetContext(), packetID)
   582  				suite.Require().Equal(relayer, suite.chainA.SenderAccount().GetAddress().String())
   583  
   584  			case !tc.forwardRelayer:
   585  				expectedAck := types.IncentivizedAcknowledgement{
   586  					AppAcknowledgement:    ibcmock.MockAcknowledgement.Acknowledgement(),
   587  					ForwardRelayerAddress: "",
   588  					UnderlyingAppSuccess:  true,
   589  				}
   590  				suite.Require().Equal(expectedAck, result)
   591  			}
   592  		})
   593  	}
   594  }
   595  
   596  func (suite *FeeTestSuite) TestOnAcknowledgementPacket() {
   597  	var (
   598  		ack                 []byte
   599  		packetID            channeltypes.PacketId
   600  		packetFee           types.PacketFee
   601  		refundAddr          sdk.AccAddress
   602  		relayerAddr         sdk.AccAddress
   603  		expRefundAccBalance sdk.Coins
   604  		expPayeeAccBalance  sdk.Coins
   605  	)
   606  
   607  	testCases := []struct {
   608  		name      string
   609  		malleate  func()
   610  		expPass   bool
   611  		expResult func()
   612  	}{
   613  		{
   614  			"success",
   615  			func() {
   616  				// retrieve the relayer acc balance and add the expected recv and ack fees
   617  				relayerAccBalance := sdk.NewCoins(suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), relayerAddr, sdk.DefaultBondDenom))
   618  				expPayeeAccBalance = relayerAccBalance.Add(packetFee.Fee.RecvFee.ToCoins()...).Add(packetFee.Fee.AckFee.ToCoins()...)
   619  
   620  				// retrieve the refund acc balance and add the expected timeout fees
   621  				refundAccBalance := sdk.NewCoins(suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), refundAddr, sdk.DefaultBondDenom))
   622  				expRefundAccBalance = refundAccBalance.Add(packetFee.Fee.TimeoutFee.ToCoins()...)
   623  			},
   624  			true,
   625  			func() {
   626  				// assert that the packet fees have been distributed
   627  				found := suite.chainA.GetSimApp().IBCFeeKeeper.HasFeesInEscrow(suite.chainA.GetContext(), packetID)
   628  				suite.Require().False(found)
   629  
   630  				relayerAccBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), relayerAddr, sdk.DefaultBondDenom)
   631  				suite.Require().Equal(expPayeeAccBalance, sdk.NewCoins(relayerAccBalance))
   632  
   633  				refundAccBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), refundAddr, sdk.DefaultBondDenom)
   634  				suite.Require().Equal(expRefundAccBalance, sdk.NewCoins(refundAccBalance))
   635  			},
   636  		},
   637  		{
   638  			"success: with registered payee address",
   639  			func() {
   640  				payeeAddr := suite.chainA.SenderAccounts()[2].SenderAccount.GetAddress()
   641  				suite.chainA.GetSimApp().IBCFeeKeeper.SetPayeeAddress(
   642  					suite.chainA.GetContext(),
   643  					suite.chainA.SenderAccounts()[0].SenderAccount.GetAddress().String(),
   644  					payeeAddr.String(),
   645  					suite.path.EndpointA.ChannelID,
   646  				)
   647  
   648  				// reassign ack.ForwardRelayerAddress to the registered payee address
   649  				ack = types.NewIncentivizedAcknowledgement(payeeAddr.String(), ibcmock.MockAcknowledgement.Acknowledgement(), true).Acknowledgement()
   650  
   651  				// retrieve the payee acc balance and add the expected recv and ack fees
   652  				payeeAccBalance := sdk.NewCoins(suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), payeeAddr, sdk.DefaultBondDenom))
   653  				expPayeeAccBalance = payeeAccBalance.Add(packetFee.Fee.RecvFee.ToCoins()...).Add(packetFee.Fee.AckFee.ToCoins()...)
   654  
   655  				// retrieve the refund acc balance and add the expected timeout fees
   656  				refundAccBalance := sdk.NewCoins(suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), refundAddr, sdk.DefaultBondDenom))
   657  				expRefundAccBalance = refundAccBalance.Add(packetFee.Fee.TimeoutFee.ToCoins()...)
   658  				fmt.Println(expPayeeAccBalance.String(), refundAccBalance.String(), expRefundAccBalance.String())
   659  			},
   660  			true,
   661  			func() {
   662  				// assert that the packet fees have been distributed
   663  				found := suite.chainA.GetSimApp().IBCFeeKeeper.HasFeesInEscrow(suite.chainA.GetContext(), packetID)
   664  				suite.Require().False(found)
   665  
   666  				payeeAddr := suite.chainA.SenderAccounts()[2].SenderAccount.GetAddress()
   667  				payeeAccBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), payeeAddr, sdk.DefaultBondDenom)
   668  				fmt.Println(expPayeeAccBalance.String(), payeeAccBalance.String())
   669  				suite.Require().Equal(expPayeeAccBalance, sdk.NewCoins(payeeAccBalance))
   670  
   671  				refundAccBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), refundAddr, sdk.DefaultBondDenom)
   672  				suite.Require().Equal(expRefundAccBalance, sdk.NewCoins(refundAccBalance))
   673  			},
   674  		},
   675  		{
   676  			"success: no op without a packet fee",
   677  			func() {
   678  				suite.chainA.GetSimApp().IBCFeeKeeper.DeleteFeesInEscrow(suite.chainA.GetContext(), packetID)
   679  
   680  				ack = types.IncentivizedAcknowledgement{
   681  					AppAcknowledgement:    ibcmock.MockAcknowledgement.Acknowledgement(),
   682  					ForwardRelayerAddress: "",
   683  				}.Acknowledgement()
   684  			},
   685  			true,
   686  			func() {
   687  				found := suite.chainA.GetSimApp().IBCFeeKeeper.HasFeesInEscrow(suite.chainA.GetContext(), packetID)
   688  				suite.Require().False(found)
   689  			},
   690  		},
   691  		{
   692  			"success: channel is not fee enabled",
   693  			func() {
   694  				suite.chainA.GetSimApp().IBCFeeKeeper.DeleteFeeEnabled(suite.chainA.GetContext(), suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID)
   695  				ack = ibcmock.MockAcknowledgement.Acknowledgement()
   696  			},
   697  			true,
   698  			func() {},
   699  		},
   700  		{
   701  			"success: fee module is disabled, skip fee logic",
   702  			func() {
   703  				lockFeeModule(suite.chainA)
   704  			},
   705  			true,
   706  			func() {
   707  				suite.Require().Equal(true, suite.chainA.GetSimApp().IBCFeeKeeper.IsLocked(suite.chainA.GetContext()))
   708  			},
   709  		},
   710  		{
   711  			"success: fail to distribute recv fee (blocked address), returned to refund account",
   712  			func() {
   713  				blockedAddr := suite.chainA.GetSimApp().SupplyKeeper.GetModuleAccount(suite.chainA.GetContext(), transfertypes.ModuleName).GetAddress()
   714  
   715  				// reassign ack.ForwardRelayerAddress to a blocked address
   716  				ack = types.NewIncentivizedAcknowledgement(blockedAddr.String(), ibcmock.MockAcknowledgement.Acknowledgement(), true).Acknowledgement()
   717  
   718  				// retrieve the relayer acc balance and add the expected ack fees
   719  				relayerAccBalance := sdk.NewCoins(suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), relayerAddr, sdk.DefaultBondDenom))
   720  				expPayeeAccBalance = relayerAccBalance.Add(packetFee.Fee.AckFee.ToCoins()...)
   721  
   722  				// retrieve the refund acc balance and add the expected recv fees and timeout fees
   723  				refundAccBalance := sdk.NewCoins(suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), refundAddr, sdk.DefaultBondDenom))
   724  				expRefundAccBalance = refundAccBalance.Add(packetFee.Fee.RecvFee.ToCoins()...).Add(packetFee.Fee.TimeoutFee.ToCoins()...)
   725  			},
   726  			true,
   727  			func() {
   728  				// assert that the packet fees have been distributed
   729  				found := suite.chainA.GetSimApp().IBCFeeKeeper.HasFeesInEscrow(suite.chainA.GetContext(), packetID)
   730  				suite.Require().False(found)
   731  
   732  				relayerAccBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), relayerAddr, sdk.DefaultBondDenom)
   733  				suite.Require().Equal(expPayeeAccBalance, sdk.NewCoins(relayerAccBalance))
   734  
   735  				refundAccBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), refundAddr, sdk.DefaultBondDenom)
   736  				suite.Require().Equal(expRefundAccBalance, sdk.NewCoins(refundAccBalance))
   737  			},
   738  		},
   739  		{
   740  			"fail: fee distribution fails and fee module is locked when escrow account does not have sufficient funds",
   741  			func() {
   742  				err := suite.chainA.GetSimApp().SupplyKeeper.SendCoinsFromModuleToAccount(suite.chainA.GetContext(), types.ModuleName, suite.chainA.SenderAccount().GetAddress(), smallAmount.ToCoins())
   743  				suite.Require().NoError(err)
   744  			},
   745  			true,
   746  			func() {
   747  				suite.Require().Equal(true, suite.chainA.GetSimApp().IBCFeeKeeper.IsLocked(suite.chainA.GetContext()))
   748  			},
   749  		},
   750  		{
   751  			"ack wrong format",
   752  			func() {
   753  				ack = []byte("unsupported acknowledgement format")
   754  			},
   755  			false,
   756  			func() {},
   757  		},
   758  		{
   759  			"invalid registered payee address",
   760  			func() {
   761  				payeeAddr := "invalid-address"
   762  				suite.chainA.GetSimApp().IBCFeeKeeper.SetPayeeAddress(
   763  					suite.chainA.GetContext(),
   764  					suite.chainA.SenderAccounts()[0].SenderAccount.GetAddress().String(),
   765  					payeeAddr,
   766  					suite.path.EndpointA.ChannelID,
   767  				)
   768  			},
   769  			false,
   770  			func() {},
   771  		},
   772  		{
   773  			"application callback fails",
   774  			func() {
   775  				suite.chainA.GetSimApp().FeeMockModule.IBCApp.OnAcknowledgementPacket = func(_ sdk.Context, _ channeltypes.Packet, _ []byte, _ sdk.AccAddress) error {
   776  					return fmt.Errorf("mock fee app callback fails")
   777  				}
   778  			},
   779  			false,
   780  			func() {},
   781  		},
   782  	}
   783  
   784  	for _, tc := range testCases {
   785  		tc := tc
   786  		suite.Run(tc.name, func() {
   787  			suite.SetupTest()
   788  			suite.coordinator.Setup(suite.path)
   789  			err := suite.chainA.GetSimApp().SupplyKeeper.SendCoins(suite.chainA.GetContext(), suite.chainA.SenderAccount().GetAddress(), suite.chainA.SenderAccounts()[0].SenderAccount.GetAddress(), sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))))
   790  			suite.Require().NoError(err)
   791  			err = suite.chainA.GetSimApp().SupplyKeeper.SendCoins(suite.chainA.GetContext(), suite.chainA.SenderAccount().GetAddress(), suite.chainA.SenderAccounts()[1].SenderAccount.GetAddress(), sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))))
   792  			suite.Require().NoError(err)
   793  			relayerAddr = suite.chainA.SenderAccounts()[0].SenderAccount.GetAddress()
   794  			refundAddr = suite.chainA.SenderAccounts()[1].SenderAccount.GetAddress()
   795  
   796  			packet := suite.CreateMockPacket()
   797  			packetID = channeltypes.NewPacketId(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence())
   798  			packetFee = types.NewPacketFee(types.NewFee(defaultRecvFee, defaultAckFee, defaultTimeoutFee), refundAddr.String(), nil)
   799  
   800  			suite.chainA.GetSimApp().IBCFeeKeeper.SetFeesInEscrow(suite.chainA.GetContext(), packetID, types.NewPacketFees([]types.PacketFee{packetFee}))
   801  
   802  			err = suite.chainA.GetSimApp().SupplyKeeper.SendCoinsFromAccountToModule(suite.chainA.GetContext(), refundAddr, types.ModuleName, packetFee.Fee.Total().ToCoins())
   803  			suite.Require().NoError(err)
   804  
   805  			ack = types.NewIncentivizedAcknowledgement(relayerAddr.String(), ibcmock.MockAcknowledgement.Acknowledgement(), true).Acknowledgement()
   806  
   807  			tc.malleate() // malleate mutates test data
   808  
   809  			// retrieve module callbacks
   810  			module, _, err := suite.chainA.GetSimApp().GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), ibctesting.MockFeePort)
   811  			suite.Require().NoError(err)
   812  
   813  			cbs, ok := suite.chainA.GetSimApp().GetIBCKeeper().Router.GetRoute(module)
   814  			suite.Require().True(ok)
   815  
   816  			err = cbs.OnAcknowledgementPacket(suite.chainA.GetContext(), packet, ack, relayerAddr)
   817  
   818  			if tc.expPass {
   819  				suite.Require().NoError(err)
   820  			} else {
   821  				suite.Require().Error(err)
   822  			}
   823  
   824  			tc.expResult()
   825  		})
   826  	}
   827  }
   828  
   829  func (suite *FeeTestSuite) TestOnTimeoutPacket() {
   830  	var (
   831  		packetID            channeltypes.PacketId
   832  		packetFee           types.PacketFee
   833  		refundAddr          sdk.AccAddress
   834  		relayerAddr         sdk.AccAddress
   835  		expRefundAccBalance sdk.Coins
   836  		expPayeeAccBalance  sdk.Coins
   837  	)
   838  
   839  	testCases := []struct {
   840  		name      string
   841  		malleate  func()
   842  		expPass   bool
   843  		expResult func()
   844  	}{
   845  		{
   846  			"success",
   847  			func() {
   848  				// retrieve the relayer acc balance and add the expected timeout fees
   849  				relayerAccBalance := sdk.NewCoins(suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), relayerAddr, sdk.DefaultBondDenom))
   850  				expPayeeAccBalance = relayerAccBalance.Add(packetFee.Fee.TimeoutFee.ToCoins()...)
   851  
   852  				// retrieve the refund acc balance and add the expected recv and ack fees
   853  				refundAccBalance := sdk.NewCoins(suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), refundAddr, sdk.DefaultBondDenom))
   854  				expRefundAccBalance = refundAccBalance.Add(packetFee.Fee.RecvFee.ToCoins()...).Add(packetFee.Fee.AckFee.ToCoins()...)
   855  			},
   856  			true,
   857  			func() {
   858  				// assert that the packet fees have been distributed
   859  				found := suite.chainA.GetSimApp().IBCFeeKeeper.HasFeesInEscrow(suite.chainA.GetContext(), packetID)
   860  				suite.Require().False(found)
   861  
   862  				relayerAccBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), relayerAddr, sdk.DefaultBondDenom)
   863  				suite.Require().Equal(expPayeeAccBalance, sdk.NewCoins(relayerAccBalance))
   864  
   865  				refundAccBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), refundAddr, sdk.DefaultBondDenom)
   866  				suite.Require().Equal(expRefundAccBalance, sdk.NewCoins(refundAccBalance))
   867  			},
   868  		},
   869  		{
   870  			"success: with registered payee address",
   871  			func() {
   872  				payeeAddr := suite.chainA.SenderAccounts()[2].SenderAccount.GetAddress()
   873  				suite.chainA.GetSimApp().IBCFeeKeeper.SetPayeeAddress(
   874  					suite.chainA.GetContext(),
   875  					suite.chainA.SenderAccount().GetAddress().String(),
   876  					payeeAddr.String(),
   877  					suite.path.EndpointA.ChannelID,
   878  				)
   879  
   880  				// retrieve the relayer acc balance and add the expected timeout fees
   881  				payeeAccBalance := sdk.NewCoins(suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), payeeAddr, sdk.DefaultBondDenom))
   882  				expPayeeAccBalance = payeeAccBalance.Add(packetFee.Fee.TimeoutFee.ToCoins()...)
   883  
   884  				// retrieve the refund acc balance and add the expected recv and ack fees
   885  				refundAccBalance := sdk.NewCoins(suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), refundAddr, sdk.DefaultBondDenom))
   886  				expRefundAccBalance = refundAccBalance.Add(packetFee.Fee.RecvFee.ToCoins()...).Add(packetFee.Fee.AckFee.ToCoins()...)
   887  			},
   888  			true,
   889  			func() {
   890  				// assert that the packet fees have been distributed
   891  				found := suite.chainA.GetSimApp().IBCFeeKeeper.HasFeesInEscrow(suite.chainA.GetContext(), packetID)
   892  				suite.Require().False(found)
   893  
   894  				payeeAddr := suite.chainA.SenderAccounts()[1].SenderAccount.GetAddress()
   895  				payeeAccBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), payeeAddr, sdk.DefaultBondDenom)
   896  				fmt.Println(expPayeeAccBalance.String())
   897  				fmt.Println(payeeAccBalance.String())
   898  				suite.Require().Equal(expPayeeAccBalance, sdk.NewCoins(payeeAccBalance))
   899  
   900  				refundAccBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), refundAddr, sdk.DefaultBondDenom)
   901  				suite.Require().Equal(expRefundAccBalance, sdk.NewCoins(refundAccBalance))
   902  			},
   903  		},
   904  		{
   905  			"success: channel is not fee enabled",
   906  			func() {
   907  				suite.chainA.GetSimApp().IBCFeeKeeper.DeleteFeeEnabled(suite.chainA.GetContext(), suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID)
   908  			},
   909  			true,
   910  			func() {},
   911  		},
   912  		{
   913  			"success: fee module is disabled, skip fee logic",
   914  			func() {
   915  				lockFeeModule(suite.chainA)
   916  			},
   917  			true,
   918  			func() {
   919  				suite.Require().Equal(true, suite.chainA.GetSimApp().IBCFeeKeeper.IsLocked(suite.chainA.GetContext()))
   920  			},
   921  		},
   922  		{
   923  			"success: no op if identified packet fee doesn't exist",
   924  			func() {
   925  				suite.chainA.GetSimApp().IBCFeeKeeper.DeleteFeesInEscrow(suite.chainA.GetContext(), packetID)
   926  			},
   927  			true,
   928  			func() {},
   929  		},
   930  		{
   931  			"success: fail to distribute timeout fee (blocked address), returned to refund account",
   932  			func() {
   933  				relayerAddr = suite.chainA.GetSimApp().SupplyKeeper.GetModuleAccount(suite.chainA.GetContext(), transfertypes.ModuleName).GetAddress()
   934  			},
   935  			true,
   936  			func() {},
   937  		},
   938  		{
   939  			"fee distribution fails and fee module is locked when escrow account does not have sufficient funds",
   940  			func() {
   941  				err := suite.chainA.GetSimApp().SupplyKeeper.SendCoinsFromModuleToAccount(suite.chainA.GetContext(), types.ModuleName, suite.chainA.SenderAccount().GetAddress(), smallAmount.ToCoins())
   942  				suite.Require().NoError(err)
   943  			},
   944  			true,
   945  			func() {
   946  				suite.Require().Equal(true, suite.chainA.GetSimApp().IBCFeeKeeper.IsLocked(suite.chainA.GetContext()))
   947  			},
   948  		},
   949  		{
   950  			"invalid registered payee address",
   951  			func() {
   952  				payeeAddr := "invalid-address"
   953  				suite.chainA.GetSimApp().IBCFeeKeeper.SetPayeeAddress(
   954  					suite.chainA.GetContext(),
   955  					suite.chainA.SenderAccount().GetAddress().String(),
   956  					payeeAddr,
   957  					suite.path.EndpointA.ChannelID,
   958  				)
   959  			},
   960  			false,
   961  			func() {},
   962  		},
   963  		{
   964  			"application callback fails",
   965  			func() {
   966  				suite.chainA.GetSimApp().FeeMockModule.IBCApp.OnTimeoutPacket = func(_ sdk.Context, _ channeltypes.Packet, _ sdk.AccAddress) error {
   967  					return fmt.Errorf("mock fee app callback fails")
   968  				}
   969  			},
   970  			false,
   971  			func() {},
   972  		},
   973  	}
   974  
   975  	for _, tc := range testCases {
   976  		tc := tc
   977  		suite.Run(tc.name, func() {
   978  			suite.SetupTest()
   979  			suite.coordinator.Setup(suite.path)
   980  
   981  			relayerAddr = suite.chainA.SenderAccount().GetAddress()
   982  			refundAddr = suite.chainA.SenderAccounts()[1].SenderAccount.GetAddress()
   983  
   984  			packet := suite.CreateMockPacket()
   985  			packetID = channeltypes.NewPacketId(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence())
   986  			packetFee = types.NewPacketFee(types.NewFee(defaultRecvFee, defaultAckFee, defaultTimeoutFee), refundAddr.String(), nil)
   987  
   988  			suite.chainA.GetSimApp().IBCFeeKeeper.SetFeesInEscrow(suite.chainA.GetContext(), packetID, types.NewPacketFees([]types.PacketFee{packetFee}))
   989  			err := suite.chainA.GetSimApp().SupplyKeeper.SendCoinsFromAccountToModule(suite.chainA.GetContext(), suite.chainA.SenderAccount().GetAddress(), types.ModuleName, packetFee.Fee.Total().ToCoins())
   990  			suite.Require().NoError(err)
   991  
   992  			tc.malleate() // malleate mutates test data
   993  
   994  			// retrieve module callbacks
   995  			module, _, err := suite.chainA.GetSimApp().GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), ibctesting.MockFeePort)
   996  			suite.Require().NoError(err)
   997  
   998  			cbs, ok := suite.chainA.GetSimApp().GetIBCKeeper().Router.GetRoute(module)
   999  			suite.Require().True(ok)
  1000  
  1001  			err = cbs.OnTimeoutPacket(suite.chainA.GetContext(), packet, relayerAddr)
  1002  
  1003  			if tc.expPass {
  1004  				suite.Require().NoError(err)
  1005  			} else {
  1006  				suite.Require().Error(err)
  1007  			}
  1008  
  1009  			tc.expResult()
  1010  		})
  1011  	}
  1012  }
  1013  
  1014  func (suite *FeeTestSuite) TestGetAppVersion() {
  1015  	var (
  1016  		portID        string
  1017  		channelID     string
  1018  		expAppVersion string
  1019  	)
  1020  	testCases := []struct {
  1021  		name     string
  1022  		malleate func()
  1023  		expFound bool
  1024  	}{
  1025  		{
  1026  			"success for fee enabled channel",
  1027  			func() {
  1028  				expAppVersion = ibcmock.Version
  1029  			},
  1030  			true,
  1031  		},
  1032  		{
  1033  			"success for non fee enabled channel",
  1034  			func() {
  1035  				path := ibctesting.NewPath(suite.chainA, suite.chainB)
  1036  				path.EndpointA.ChannelConfig.PortID = ibctesting.MockFeePort
  1037  				path.EndpointB.ChannelConfig.PortID = ibctesting.MockFeePort
  1038  				// by default a new path uses a non fee channel
  1039  				suite.coordinator.Setup(path)
  1040  				portID = path.EndpointA.ChannelConfig.PortID
  1041  				channelID = path.EndpointA.ChannelID
  1042  
  1043  				expAppVersion = ibcmock.Version
  1044  			},
  1045  			true,
  1046  		},
  1047  		{
  1048  			"channel does not exist",
  1049  			func() {
  1050  				channelID = "does not exist"
  1051  			},
  1052  			false,
  1053  		},
  1054  	}
  1055  
  1056  	for _, tc := range testCases {
  1057  		tc := tc
  1058  		suite.Run(tc.name, func() {
  1059  			suite.SetupTest()
  1060  			suite.coordinator.Setup(suite.path)
  1061  
  1062  			portID = suite.path.EndpointA.ChannelConfig.PortID
  1063  			channelID = suite.path.EndpointA.ChannelID
  1064  
  1065  			// malleate test case
  1066  			tc.malleate()
  1067  
  1068  			module, _, err := suite.chainA.GetSimApp().GetIBCKeeper().PortKeeper.LookupModuleByPort(suite.chainA.GetContext(), ibctesting.MockFeePort)
  1069  			suite.Require().NoError(err)
  1070  
  1071  			cbs, ok := suite.chainA.GetSimApp().GetIBCKeeper().Router.GetRoute(module)
  1072  			suite.Require().True(ok)
  1073  
  1074  			feeModule := cbs.(fee.IBCMiddleware)
  1075  
  1076  			appVersion, found := feeModule.GetAppVersion(suite.chainA.GetContext(), portID, channelID)
  1077  
  1078  			if tc.expFound {
  1079  				suite.Require().True(found)
  1080  				suite.Require().Equal(expAppVersion, appVersion)
  1081  			} else {
  1082  				suite.Require().False(found)
  1083  				suite.Require().Empty(appVersion)
  1084  			}
  1085  		})
  1086  	}
  1087  }