diff --git a/e2e/tests/interchain_accounts/base_test.go b/e2e/tests/interchain_accounts/base_test.go index 2b89b6f7e70..f34b8a86622 100644 --- a/e2e/tests/interchain_accounts/base_test.go +++ b/e2e/tests/interchain_accounts/base_test.go @@ -6,6 +6,7 @@ import ( "time" ibctest "github.com/strangelove-ventures/ibctest/v6" + "github.com/strangelove-ventures/ibctest/v6/chain/cosmos" "github.com/strangelove-ventures/ibctest/v6/ibc" "github.com/strangelove-ventures/ibctest/v6/test" "github.com/stretchr/testify/suite" @@ -21,6 +22,7 @@ import ( controllertypes "github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/controller/types" icatypes "github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" ibctesting "github.com/cosmos/ibc-go/v6/testing" ) @@ -44,6 +46,13 @@ func getICAVersion(chainAVersion, chainBVersion string) string { return icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) } +// RegisterInterchainAccount will attempt to register an interchain account on the counterparty chain. +func (s *InterchainAccountsTestSuite) RegisterInterchainAccount(ctx context.Context, chain *cosmos.CosmosChain, user *ibc.Wallet, msgRegisterAccount *controllertypes.MsgRegisterInterchainAccount) { + txResp, err := s.BroadcastMessages(ctx, chain, user, msgRegisterAccount) + s.Require().NoError(err) + s.AssertValidTxResponse(txResp) +} + func (s *InterchainAccountsTestSuite) TestMsgSendTx_SuccessfulTransfer() { t := s.T() ctx := context.TODO() @@ -56,12 +65,13 @@ func (s *InterchainAccountsTestSuite) TestMsgSendTx_SuccessfulTransfer() { // setup 2 accounts: controller account on chain A, a second chain B account. // host account will be created when the ICA is registered controllerAccount := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + controllerAddress := controllerAccount.Bech32Address(chainA.Config().Bech32Prefix) chainBAccount := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) var hostAccount string t.Run("broadcast MsgRegisterInterchainAccount", func(t *testing.T) { version := getICAVersion(testconfig.GetChainATag(), testconfig.GetChainBTag()) - msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, controllerAccount.Bech32Address(chainA.Config().Bech32Prefix), version) + msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, controllerAddress, version) txResp, err := s.BroadcastMessages(ctx, chainA, controllerAccount, msgRegisterAccount) s.Require().NoError(err) @@ -74,7 +84,7 @@ func (s *InterchainAccountsTestSuite) TestMsgSendTx_SuccessfulTransfer() { t.Run("verify interchain account", func(t *testing.T) { var err error - hostAccount, err = s.QueryInterchainAccount(ctx, chainA, controllerAccount.Bech32Address(chainA.Config().Bech32Prefix), ibctesting.FirstConnectionID) + hostAccount, err = s.QueryInterchainAccount(ctx, chainA, controllerAddress, ibctesting.FirstConnectionID) s.Require().NoError(err) s.Require().NotZero(len(hostAccount)) @@ -152,12 +162,13 @@ func (s *InterchainAccountsTestSuite) TestMsgSendTx_FailedTransfer_InsufficientF // setup 2 accounts: controller account on chain A, a second chain B account. // host account will be created when the ICA is registered controllerAccount := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + controllerAddress := controllerAccount.Bech32Address(chainA.Config().Bech32Prefix) chainBAccount := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) var hostAccount string t.Run("broadcast MsgRegisterInterchainAccount", func(t *testing.T) { version := getICAVersion(testconfig.GetChainATag(), testconfig.GetChainBTag()) - msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, controllerAccount.Bech32Address(chainA.Config().Bech32Prefix), version) + msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, controllerAddress, version) txResp, err := s.BroadcastMessages(ctx, chainA, controllerAccount, msgRegisterAccount) s.Require().NoError(err) @@ -170,7 +181,7 @@ func (s *InterchainAccountsTestSuite) TestMsgSendTx_FailedTransfer_InsufficientF t.Run("verify interchain account", func(t *testing.T) { var err error - hostAccount, err = s.QueryInterchainAccount(ctx, chainA, controllerAccount.Bech32Address(chainA.Config().Bech32Prefix), ibctesting.FirstConnectionID) + hostAccount, err = s.QueryInterchainAccount(ctx, chainA, controllerAddress, ibctesting.FirstConnectionID) s.Require().NoError(err) s.Require().NotZero(len(hostAccount)) @@ -204,7 +215,7 @@ func (s *InterchainAccountsTestSuite) TestMsgSendTx_FailedTransfer_InsufficientF Memo: "e2e", } - msgSendTx := controllertypes.NewMsgSendTx(controllerAccount.Bech32Address(chainA.Config().Bech32Prefix), ibctesting.FirstConnectionID, uint64(time.Hour.Nanoseconds()), packetData) + msgSendTx := controllertypes.NewMsgSendTx(controllerAddress, ibctesting.FirstConnectionID, uint64(time.Hour.Nanoseconds()), packetData) txResp, err := s.BroadcastMessages( ctx, @@ -228,3 +239,181 @@ func (s *InterchainAccountsTestSuite) TestMsgSendTx_FailedTransfer_InsufficientF }) }) } + +func (s *InterchainAccountsTestSuite) TestMsgSubmitTx_SuccessfulTransfer_AfterReopeningICA() { + t := s.T() + ctx := context.TODO() + + // setup relayers and connection-0 between two chains + // channel-0 is a transfer channel but it will not be used in this test case + relayer, _ := s.SetupChainsRelayerAndChannel(ctx) + chainA, chainB := s.GetChains() + + // setup 2 accounts: controller account on chain A, a second chain B account. + // host account will be created when the ICA is registered + controllerAccount := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + controllerAddress := controllerAccount.Bech32Address(chainA.Config().Bech32Prefix) + chainBAccount := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + + var ( + portID string + hostAccount string + + initialChannelID = "channel-1" + channelIDAfterReopening = "channel-2" + ) + + t.Run("register interchain account", func(t *testing.T) { + var err error + version := getICAVersion(testconfig.GetChainATag(), testconfig.GetChainBTag()) + msgRegisterInterchainAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, controllerAddress, version) + s.RegisterInterchainAccount(ctx, chainA, controllerAccount, msgRegisterInterchainAccount) + portID, err = icatypes.NewControllerPortID(controllerAddress) + s.Require().NoError(err) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer) + }) + + t.Run("verify interchain account", func(t *testing.T) { + var err error + hostAccount, err = s.QueryInterchainAccount(ctx, chainA, controllerAddress, ibctesting.FirstConnectionID) + s.Require().NoError(err) + s.Require().NotZero(len(hostAccount)) + + _, err = s.QueryChannel(ctx, chainA, portID, initialChannelID) + s.Require().NoError(err) + }) + + // stop the relayer to let the submit tx message time out + t.Run("stop relayer", func(t *testing.T) { + s.StopRelayer(ctx, relayer) + }) + + t.Run("submit tx message with bank transfer message times out", func(t *testing.T) { + t.Run("fund interchain account wallet", func(t *testing.T) { + // fund the host account account so it has some $$ to send + err := chainB.SendFunds(ctx, ibctest.FaucetAccountKeyName, ibc.WalletAmount{ + Address: hostAccount, + Amount: testvalues.StartingTokenAmount, + Denom: chainB.Config().Denom, + }) + s.Require().NoError(err) + }) + + t.Run("broadcast MsgSendTx", func(t *testing.T) { + // assemble bank transfer message from host account to user account on host chain + msgSend := &banktypes.MsgSend{ + FromAddress: hostAccount, + ToAddress: chainBAccount.Bech32Address(chainB.Config().Bech32Prefix), + Amount: sdk.NewCoins(testvalues.DefaultTransferAmount(chainB.Config().Denom)), + } + + cdc := testsuite.Codec() + + bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{msgSend}) + s.Require().NoError(err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "e2e", + } + + msgSendTx := controllertypes.NewMsgSendTx(controllerAddress, ibctesting.FirstConnectionID, uint64(1), packetData) + + resp, err := s.BroadcastMessages( + ctx, + chainA, + controllerAccount, + msgSendTx, + ) + + s.AssertValidTxResponse(resp) + s.Require().NoError(err) + + // this sleep is to allow the packet to timeout + time.Sleep(1 * time.Second) + }) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer) + }) + + t.Run("verify channel is closed due to timeout on ordered channel", func(t *testing.T) { + channel, err := s.QueryChannel(ctx, chainA, portID, initialChannelID) + s.Require().NoError(err) + + s.Require().Equal(channeltypes.CLOSED, channel.State, "the channel was not in an expected state") + }) + + t.Run("verify tokens not transferred", func(t *testing.T) { + balance, err := chainB.GetBalance(ctx, chainBAccount.Bech32Address(chainB.Config().Bech32Prefix), chainB.Config().Denom) + s.Require().NoError(err) + + _, err = chainB.GetBalance(ctx, hostAccount, chainB.Config().Denom) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount + s.Require().Equal(expected, balance) + }) + + // re-register interchain account to reopen the channel now that it has been closed due to timeout + // on an ordered channel + t.Run("register interchain account", func(t *testing.T) { + version := getICAVersion(testconfig.GetChainATag(), testconfig.GetChainBTag()) + msgRegisterInterchainAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, controllerAddress, version) + s.RegisterInterchainAccount(ctx, chainA, controllerAccount, msgRegisterInterchainAccount) + + s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB)) + }) + + t.Run("verify new channel is now open and interchain account has been reregistered with the same portID", func(t *testing.T) { + channel, err := s.QueryChannel(ctx, chainA, portID, channelIDAfterReopening) + s.Require().NoError(err) + + s.Require().Equal(channeltypes.OPEN, channel.State, "the channel was not in an expected state") + }) + + t.Run("broadcast MsgSendTx", func(t *testing.T) { + // assemble bank transfer message from host account to user account on host chain + msgSend := &banktypes.MsgSend{ + FromAddress: hostAccount, + ToAddress: chainBAccount.Bech32Address(chainB.Config().Bech32Prefix), + Amount: sdk.NewCoins(testvalues.DefaultTransferAmount(chainB.Config().Denom)), + } + + cdc := testsuite.Codec() + + bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{msgSend}) + s.Require().NoError(err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "e2e", + } + + msgSendTx := controllertypes.NewMsgSendTx(controllerAddress, ibctesting.FirstConnectionID, uint64(5*time.Minute), packetData) + + resp, err := s.BroadcastMessages( + ctx, + chainA, + controllerAccount, + msgSendTx, + ) + + s.AssertValidTxResponse(resp) + s.Require().NoError(err) + }) + + t.Run("verify tokens transferred", func(t *testing.T) { + balance, err := chainB.GetBalance(ctx, chainBAccount.Bech32Address(chainB.Config().Bech32Prefix), chainB.Config().Denom) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + testvalues.StartingTokenAmount + s.Require().Equal(expected, balance) + }) +} diff --git a/e2e/testsuite/grpc_query.go b/e2e/testsuite/grpc_query.go index 3bb4dd06745..7696f630b4a 100644 --- a/e2e/testsuite/grpc_query.go +++ b/e2e/testsuite/grpc_query.go @@ -33,6 +33,20 @@ func (s *E2ETestSuite) QueryClientState(ctx context.Context, chain ibc.Chain, cl return clientState, nil } +// QueryChannel queries the channel on a given chain for the provided portID and channelID +func (s *E2ETestSuite) QueryChannel(ctx context.Context, chain ibc.Chain, portID, channelID string) (channeltypes.Channel, error) { + queryClient := s.GetChainGRCPClients(chain).ChannelQueryClient + res, err := queryClient.Channel(ctx, &channeltypes.QueryChannelRequest{ + PortId: portID, + ChannelId: channelID, + }) + if err != nil { + return channeltypes.Channel{}, err + } + + return *res.Channel, nil +} + // QueryPacketCommitment queries the packet commitment on the given chain for the provided channel and sequence. func (s *E2ETestSuite) QueryPacketCommitment(ctx context.Context, chain ibc.Chain, portID, channelID string, sequence uint64) ([]byte, error) { queryClient := s.GetChainGRCPClients(chain).ChannelQueryClient