diff --git a/e2e/go.mod b/e2e/go.mod index 5ec2fa66761..0e33ef9636d 100644 --- a/e2e/go.mod +++ b/e2e/go.mod @@ -7,6 +7,7 @@ replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alp require ( github.com/cosmos/cosmos-sdk v0.46.0 github.com/cosmos/ibc-go/v5 v5.0.0-beta1 + github.com/cosmos/interchain-accounts v0.3.1-0.20220816085955-393d8444c111 github.com/docker/docker v20.10.17+incompatible github.com/strangelove-ventures/ibctest v0.0.0-20220808203516-6cbd3743756d github.com/stretchr/testify v1.8.0 diff --git a/e2e/go.sum b/e2e/go.sum index cbc524e9dec..5b33628fe35 100644 --- a/e2e/go.sum +++ b/e2e/go.sum @@ -256,6 +256,10 @@ github.com/cosmos/iavl v0.19.0 h1:sgyrjqOkycXiN7Tuupuo4QAldKFg7Sipyfeg/IL7cps= github.com/cosmos/iavl v0.19.0/go.mod h1:l5h9pAB3m5fihB3pXVgwYqdY8aBsMagqz7T0MUjxZeA= github.com/cosmos/ibc-go/v5 v5.0.0-beta1 h1:YqC9giQlZId8Wui8xpaUFI+TpVmEupQZSoDlmxAu6yI= github.com/cosmos/ibc-go/v5 v5.0.0-beta1/go.mod h1:9mmcbzuidgX7nhafIKng/XhXAHDEnRqDjGy/60W1cvg= +github.com/cosmos/interchain-accounts v0.3.0 h1:Zu9372ze/a6HhUy5Z4Mu+C+FcnNLGrQ15aPwr6lRTeI= +github.com/cosmos/interchain-accounts v0.3.0/go.mod h1:FYF1IiAz6M/LC1Gb3Jch042IaL0AXOfU+s1ZGvR4IqI= +github.com/cosmos/interchain-accounts v0.3.1-0.20220816085955-393d8444c111 h1:5Tm0jHmyh2XDc/XlIvaFJbIvVQyRMH4EtoLzSJM/MAY= +github.com/cosmos/interchain-accounts v0.3.1-0.20220816085955-393d8444c111/go.mod h1:vNWr9YxBrI5c74jBwk9ooiUCIDvdOJeF8MQEA/z0IEk= github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4= github.com/cosmos/ledger-cosmos-go v0.11.1/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY= github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI= diff --git a/e2e/interchain_accounts_test.go b/e2e/interchain_accounts_test.go new file mode 100644 index 00000000000..c6cd858066a --- /dev/null +++ b/e2e/interchain_accounts_test.go @@ -0,0 +1,225 @@ +package e2e + +import ( + "context" + "testing" + + ibctest "github.com/strangelove-ventures/ibctest" + "github.com/strangelove-ventures/ibctest/chain/cosmos" + "github.com/strangelove-ventures/ibctest/ibc" + "github.com/strangelove-ventures/ibctest/test" + "github.com/stretchr/testify/suite" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + intertxtypes "github.com/cosmos/interchain-accounts/x/inter-tx/types" + + "github.com/cosmos/ibc-go/e2e/testconfig" + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testvalues" + ibctesting "github.com/cosmos/ibc-go/v5/testing" +) + +func TestInterchainAccountsTestSuite(t *testing.T) { + // NOTE: this is a temporary mechanism to enable this test to run alongside the simd tests. + // This will be removed in a follow up PR and properly parameterized in a github workflow. + testconfig.SetChainBinaryVersions( + "ghcr.io/cosmos/ibc-go-icad", "master", "icad", "ghcr.io/cosmos/ibc-go-icad", "master", + ) + suite.Run(t, new(InterchainAccountsTestSuite)) +} + +type InterchainAccountsTestSuite struct { + testsuite.E2ETestSuite +} + +// RegisterInterchainAccount will attempt to register an interchain account on the counterparty chain. +func (s *InterchainAccountsTestSuite) RegisterInterchainAccount(ctx context.Context, chain *cosmos.CosmosChain, user *ibctest.User, msgRegisterAccount *intertxtypes.MsgRegisterAccount) error { + txResp, err := s.BroadcastMessages(ctx, chain, user, msgRegisterAccount) + s.AssertValidTxResponse(txResp) + return err +} + +func (s *InterchainAccountsTestSuite) TestMsgSubmitTx_SuccessfulTransfer() { + 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) + chainBAccount := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + var hostAccount string + + t.Run("register interchain account", func(t *testing.T) { + version := "" // allow app to handle the version as appropriate. + msgRegisterAccount := intertxtypes.NewMsgRegisterAccount(controllerAccount.Bech32Address(chainA.Config().Bech32Prefix), ibctesting.FirstConnectionID, version) + err := s.RegisterInterchainAccount(ctx, chainA, controllerAccount, msgRegisterAccount) + 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, controllerAccount.Bech32Address(chainA.Config().Bech32Prefix), ibctesting.FirstConnectionID) + s.Require().NoError(err) + s.Require().NotZero(len(hostAccount)) + + channels, err := relayer.GetChannels(ctx, s.GetRelayerExecReporter(), chainA.Config().ChainID) + s.Require().NoError(err) + s.Require().Equal(len(channels), 2) + }) + + t.Run("interchain account executes a bank transfer on behalf of the corresponding owner account", 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 MsgSubmitTx", 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)), + } + + // assemble submitMessage tx for intertx + msgSubmitTx, err := intertxtypes.NewMsgSubmitTx( + msgSend, + ibctesting.FirstConnectionID, + controllerAccount.Bech32Address(chainA.Config().Bech32Prefix), + ) + s.Require().NoError(err) + + // broadcast submitMessage tx from controller account on chain A + // this message should trigger the sending of an ICA packet over channel-1 (channel created between controller and host) + // this ICA packet contains the assembled bank transfer message from above, which will be executed by the host account on the host chain. + resp, err := s.BroadcastMessages( + ctx, + chainA, + controllerAccount, + msgSubmitTx, + ) + + s.AssertValidTxResponse(resp) + s.Require().NoError(err) + + s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB)) + }) + + 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) + + _, err = chainB.GetBalance(ctx, hostAccount, chainB.Config().Denom) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + testvalues.StartingTokenAmount + s.Require().Equal(expected, balance) + }) + }) +} + +func (s *InterchainAccountsTestSuite) TestMsgSubmitTx_FailedTransfer_InsufficientFunds() { + 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) + chainBAccount := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + var hostAccount string + + t.Run("register interchain account", func(t *testing.T) { + version := "" // allow app to handle the version as appropriate. + msgRegisterAccount := intertxtypes.NewMsgRegisterAccount(controllerAccount.Bech32Address(chainA.Config().Bech32Prefix), ibctesting.FirstConnectionID, version) + err := s.RegisterInterchainAccount(ctx, chainA, controllerAccount, msgRegisterAccount) + 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, controllerAccount.Bech32Address(chainA.Config().Bech32Prefix), ibctesting.FirstConnectionID) + s.Require().NoError(err) + s.Require().NotZero(len(hostAccount)) + + channels, err := relayer.GetChannels(ctx, s.GetRelayerExecReporter(), chainA.Config().ChainID) + s.Require().NoError(err) + s.Require().Equal(len(channels), 2) + }) + + t.Run("fail to execute bank transfer over ICA", func(t *testing.T) { + t.Run("verify empty host wallet", func(t *testing.T) { + hostAccountBalance, err := chainB.GetBalance(ctx, hostAccount, chainB.Config().Denom) + s.Require().NoError(err) + s.Require().Zero(hostAccountBalance) + }) + + t.Run("broadcast MsgSubmitTx", func(t *testing.T) { + // assemble bank transfer message from host account to user account on host chain + transferMsg := &banktypes.MsgSend{ + FromAddress: hostAccount, + ToAddress: chainBAccount.Bech32Address(chainB.Config().Bech32Prefix), + Amount: sdk.NewCoins(testvalues.DefaultTransferAmount(chainB.Config().Denom)), + } + + // assemble submitMessage tx for intertx + submitMsg, err := intertxtypes.NewMsgSubmitTx( + transferMsg, + ibctesting.FirstConnectionID, + controllerAccount.Bech32Address(chainA.Config().Bech32Prefix), + ) + s.Require().NoError(err) + + // broadcast submitMessage tx from controller account on chain A + // this message should trigger the sending of an ICA packet over channel-1 (channel created between controller and host) + // this ICA packet contains the assembled bank transfer message from above, which will be executed by the host account on the host chain. + resp, err := s.BroadcastMessages( + ctx, + chainA, + controllerAccount, + submitMsg, + ) + + s.AssertValidTxResponse(resp) + s.Require().NoError(err) + }) + + t.Run("packets are relayed", func(t *testing.T) { + channels, err := relayer.GetChannels(ctx, s.GetRelayerExecReporter(), chainA.Config().ChainID) + s.Require().NoError(err) + s.AssertPacketRelayed(ctx, chainA, channels[1].PortID, channels[1].ChannelID, 1) + }) + + t.Run("verify balance is the same", func(t *testing.T) { + balance, err := chainB.GetBalance(ctx, chainBAccount.Bech32Address(chainB.Config().Bech32Prefix), chainB.Config().Denom) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount + s.Require().Equal(expected, balance) + }) + }) +} diff --git a/e2e/testconfig/testconfig.go b/e2e/testconfig/testconfig.go index 7613ec470d4..c1aa1c1e0c5 100644 --- a/e2e/testconfig/testconfig.go +++ b/e2e/testconfig/testconfig.go @@ -18,8 +18,13 @@ const ( // ChainBSimdTagEnv specifies the tag that Chain B will use. If unspecified // the value will default to the same value as Chain A. ChainBSimdTagEnv = "CHAIN_B_SIMD_TAG" - GoRelayerTagEnv = "RLY_TAG" - + // GoRelayerTagEnv specifies the go relayer version. Defaults to "main" + GoRelayerTagEnv = "RLY_TAG" + // ChainBinary binary is the binary that will be used for the chains. + ChainBinary = "CHAIN_BINARY" + // defaultBinary is the default binary that will be used by the chains. + defaultBinary = "simd" + // defaultSimdImage is the default image that will be used for the chain if none are specified. defaultSimdImage = "ghcr.io/cosmos/ibc-go-simd" defaultRlyTag = "main" ) @@ -32,12 +37,18 @@ type TestConfig struct { } type ChainConfig struct { - Image string - Tag string + Image string + Tag string + Binary string } // FromEnv returns a TestConfig constructed from environment variables. func FromEnv() TestConfig { + chainBinary, ok := os.LookupEnv(ChainBinary) + if !ok { + chainBinary = defaultBinary + } + chainASimdImage, ok := os.LookupEnv(ChainASimdImageEnv) if !ok { chainASimdImage = defaultSimdImage @@ -45,7 +56,7 @@ func FromEnv() TestConfig { chainASimdTag, ok := os.LookupEnv(ChainASimdTagEnv) if !ok { - panic(fmt.Sprintf("must specify simd version for test with environment variable [%s]", ChainASimdTagEnv)) + panic(fmt.Sprintf("must specify %s version for test with environment variable [%s]", chainBinary, ChainASimdTagEnv)) } chainBSimdImage, ok := os.LookupEnv(ChainBSimdImageEnv) @@ -65,12 +76,14 @@ func FromEnv() TestConfig { return TestConfig{ ChainAConfig: ChainConfig{ - Image: chainASimdImage, - Tag: chainASimdTag, + Image: chainASimdImage, + Tag: chainASimdTag, + Binary: chainBinary, }, ChainBConfig: ChainConfig{ - Image: chainBSimdImage, - Tag: chainBSimdTag, + Image: chainBSimdImage, + Tag: chainBSimdTag, + Binary: chainBinary, }, RlyTag: rlyTag, } @@ -111,7 +124,7 @@ func newDefaultSimappConfig(cc ChainConfig, name, chainID, denom string) ibc.Cha Version: cc.Tag, }, }, - Bin: "simd", + Bin: cc.Binary, Bech32Prefix: "cosmos", Denom: denom, GasPrices: fmt.Sprintf("0.00%s", denom), @@ -120,3 +133,12 @@ func newDefaultSimappConfig(cc ChainConfig, name, chainID, denom string) ibc.Cha NoHostMount: false, } } + +// SetChainBinaryVersions is a helper function for local cross-version testing +func SetChainBinaryVersions(chainaSimdImg, chainaSimdTag, chainBinary, chainbSimdImg, chainbSimdTag string) { + os.Setenv("CHAIN_A_SIMD_IMAGE", chainaSimdImg) + os.Setenv("CHAIN_A_SIMD_TAG", chainaSimdTag) + os.Setenv("CHAIN_B_SIMD_IMAGE", chainbSimdImg) + os.Setenv("CHAIN_B_SIMD_TAG", chainbSimdTag) + os.Setenv("CHAIN_BINARY", chainBinary) +} diff --git a/e2e/testsuite/grpc_query.go b/e2e/testsuite/grpc_query.go index e3cd50a8979..85ae0e179dc 100644 --- a/e2e/testsuite/grpc_query.go +++ b/e2e/testsuite/grpc_query.go @@ -3,6 +3,7 @@ package testsuite import ( "context" + intertxtypes "github.com/cosmos/interchain-accounts/x/inter-tx/types" "github.com/strangelove-ventures/ibctest/ibc" clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types" @@ -41,3 +42,16 @@ func (s *E2ETestSuite) QueryPacketCommitment(ctx context.Context, chain ibc.Chai } return res.Commitment, nil } + +// QueryInterchainAccount queries the interchain account for the given owner and connectionId. +func (s *E2ETestSuite) QueryInterchainAccount(ctx context.Context, chain ibc.Chain, owner, connectionId string) (string, error) { + queryClient := s.GetChainGRCPClients(chain).ICAQueryClient + res, err := queryClient.InterchainAccount(ctx, &intertxtypes.QueryInterchainAccountRequest{ + Owner: owner, + ConnectionId: connectionId, + }) + if err != nil { + return "", err + } + return res.InterchainAccountAddress, nil +} diff --git a/e2e/testsuite/relayer.go b/e2e/testsuite/relayer.go index 9e9c5ccafe6..c420318f6f5 100644 --- a/e2e/testsuite/relayer.go +++ b/e2e/testsuite/relayer.go @@ -18,7 +18,7 @@ const ( // newCosmosRelayer returns an instance of the go relayer. func newCosmosRelayer(t *testing.T, tc testconfig.TestConfig, logger *zap.Logger, dockerClient *dockerclient.Client, network string) ibc.Relayer { - return ibctest.NewBuiltinRelayerFactory(ibc.CosmosRly, logger, relayer.CustomDockerImage(cosmosRelayerRepository, tc.RlyTag)).Build( + return ibctest.NewBuiltinRelayerFactory(ibc.CosmosRly, logger, relayer.CustomDockerImage(cosmosRelayerRepository, tc.RlyTag), relayer.StartupFlags("-p", "events")).Build( t, dockerClient, network, ) } diff --git a/e2e/testsuite/testsuite.go b/e2e/testsuite/testsuite.go index 12257330fec..93b01896581 100644 --- a/e2e/testsuite/testsuite.go +++ b/e2e/testsuite/testsuite.go @@ -7,6 +7,7 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" + intertxtypes "github.com/cosmos/interchain-accounts/x/inter-tx/types" dockerclient "github.com/docker/docker/client" "github.com/strangelove-ventures/ibctest" "github.com/strangelove-ventures/ibctest/chain/cosmos" @@ -51,6 +52,7 @@ type GRPCClients struct { ClientQueryClient clienttypes.QueryClient ChannelQueryClient channeltypes.QueryClient FeeQueryClient feetypes.QueryClient + ICAQueryClient intertxtypes.QueryClient } // path is a pairing of two chains which will be used in a test. @@ -116,7 +118,7 @@ func (s *E2ETestSuite) SetupChainsRelayerAndChannel(ctx context.Context, channel opt(&channelOptions) } - eRep := s.getRelayerExecReporter() + eRep := s.GetRelayerExecReporter() s.Require().NoError(ic.Build(ctx, eRep, ibctest.InterchainBuildOptions{ TestName: s.T().Name(), Client: s.DockerClient, @@ -281,6 +283,7 @@ func (s *E2ETestSuite) initGRPCClients(chain *cosmos.CosmosChain) { ClientQueryClient: clienttypes.NewQueryClient(grpcConn), ChannelQueryClient: channeltypes.NewQueryClient(grpcConn), FeeQueryClient: feetypes.NewQueryClient(grpcConn), + ICAQueryClient: intertxtypes.NewQueryClient(grpcConn), } } @@ -319,9 +322,9 @@ func (s *E2ETestSuite) createCosmosChains(chainOptions testconfig.ChainOptions) return chainA, chainB } -// getRelayerExecReporter returns a testreporter.RelayerExecReporter instances +// GetRelayerExecReporter returns a testreporter.RelayerExecReporter instances // using the current test's testing.T. -func (s *E2ETestSuite) getRelayerExecReporter() *testreporter.RelayerExecReporter { +func (s *E2ETestSuite) GetRelayerExecReporter() *testreporter.RelayerExecReporter { rep := testreporter.NewNopReporter() return rep.RelayerExecReporter(s.T()) }