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 }