diff --git a/e2e/tests/transfer/forwarding_test.go b/e2e/tests/transfer/forwarding_test.go index f7a2c33c15f..6bb21f49042 100644 --- a/e2e/tests/transfer/forwarding_test.go +++ b/e2e/tests/transfer/forwarding_test.go @@ -289,3 +289,74 @@ func (s *TransferForwardingTestSuite) TestChannelUpgradeForwarding_Succeeds() { s.Require().Equal(expected, actualBalance.Int64()) }) } + +// TestFailedForwarding tests the scenario in which the packet is sent from +// A to C (through B) but it can't reach C (we use an invalid address). +func (s *TransferForwardingTestSuite) TestFailedForwarding() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + t.Parallel() + relayer := s.CreateDefaultPaths(testName) + chains := s.GetAllChains() + + chainA, chainB, chainC := chains[0], chains[1], chains[2] + + chainADenom := chainA.Config().Denom + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + + chainCWallet := s.CreateUserOnChainC(ctx, testvalues.StartingTokenAmount) + + channelAtoB := s.GetChainAChannelForTest(testName) + channelBtoC := s.GetChannelsForTest(chainB, testName)[1] + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("native IBC token transfer from chainA to invalid address through B", func(t *testing.T) { + inFiveMinutes := time.Now().Add(5 * time.Minute).UnixNano() + forwarding := transfertypes.NewForwarding(false, transfertypes.NewHop(channelBtoC.PortID, channelBtoC.ChannelID)) + resp := s.Transfer(ctx, chainA, chainAWallet, channelAtoB.PortID, channelAtoB.ChannelID, testvalues.DefaultTransferCoins(chainADenom), chainAAddress, testvalues.InvalidAddress, clienttypes.ZeroHeight(), uint64(inFiveMinutes), "", forwarding) + s.AssertTxSuccess(resp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelAtoB.PortID, channelAtoB.ChannelID, 1) + }) + + t.Run("token transfer amount unescrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("balances for B and C have not changed", func(t *testing.T) { + chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelAtoB.Counterparty.PortID, channelAtoB.Counterparty.ChannelID) // IBC token sent to chainB + chainBBalance, err := testsuite.GetChainBalanceForDenom(ctx, chainB, chainBIBCToken.IBCDenom(), chainBWallet) + s.Require().NoError(err) + s.Require().Zero(chainBBalance) + + chainCIBCToken := testsuite.GetIBCToken(chainBIBCToken.IBCDenom(), channelBtoC.Counterparty.PortID, channelBtoC.Counterparty.ChannelID) // IBC token sent to chainC + chainCBalance, err := testsuite.GetChainBalanceForDenom(ctx, chainC, chainCIBCToken.IBCDenom(), chainCWallet) + s.Require().NoError(err) + s.Require().Zero(chainCBalance) + }) +} diff --git a/e2e/testsuite/testsuite.go b/e2e/testsuite/testsuite.go index 6ea1d22a8bd..4237afacbec 100644 --- a/e2e/testsuite/testsuite.go +++ b/e2e/testsuite/testsuite.go @@ -564,25 +564,20 @@ func (s *E2ETestSuite) createWalletOnChainIndex(ctx context.Context, amount, cha // GetChainANativeBalance gets the balance of a given user on chain A. func (s *E2ETestSuite) GetChainANativeBalance(ctx context.Context, user ibc.Wallet) (int64, error) { chainA := s.GetAllChains()[0] - - balanceResp, err := query.GRPCQuery[banktypes.QueryBalanceResponse](ctx, chainA, &banktypes.QueryBalanceRequest{ - Address: user.FormattedAddress(), - Denom: chainA.Config().Denom, - }) - if err != nil { - return 0, err - } - - return balanceResp.Balance.Amount.Int64(), nil + return GetChainBalanceForDenom(ctx, chainA, chainA.Config().Denom, user) } // GetChainBNativeBalance gets the balance of a given user on chain B. func (s *E2ETestSuite) GetChainBNativeBalance(ctx context.Context, user ibc.Wallet) (int64, error) { chainB := s.GetAllChains()[1] + return GetChainBalanceForDenom(ctx, chainB, chainB.Config().Denom, user) +} - balanceResp, err := query.GRPCQuery[banktypes.QueryBalanceResponse](ctx, chainB, &banktypes.QueryBalanceRequest{ +// GetChainBalanceForDenom returns the balance for a given denom given a chain. +func GetChainBalanceForDenom(ctx context.Context, chain ibc.Chain, denom string, user ibc.Wallet) (int64, error) { + balanceResp, err := query.GRPCQuery[banktypes.QueryBalanceResponse](ctx, chain, &banktypes.QueryBalanceRequest{ Address: user.FormattedAddress(), - Denom: chainB.Config().Denom, + Denom: denom, }) if err != nil { return 0, err