diff --git a/e2e/relayer/relayer.go b/e2e/relayer/relayer.go index 7467a9f47f3..f96af34a7c9 100644 --- a/e2e/relayer/relayer.go +++ b/e2e/relayer/relayer.go @@ -15,8 +15,8 @@ const ( Rly = "rly" Hermes = "hermes" - cosmosRelayerRepository = "ghcr.io/cosmos/relayer" - cosmosRelayerUser = "100:1000" // docker run -it --rm --entrypoint echo ghcr.io/cosmos/relayer "$(id -u):$(id -g)" + cosmosRelayerRepository = "damiannolan/rly" //"ghcr.io/cosmos/relayer" + cosmosRelayerUser = "100:1000" // docker run -it --rm --entrypoint echo ghcr.io/cosmos/relayer "$(id -u):$(id -g)" ) // Config holds configuration values for the relayer used in the tests. diff --git a/e2e/testconfig/testconfig.go b/e2e/testconfig/testconfig.go index 0564d93175c..74255f838b5 100644 --- a/e2e/testconfig/testconfig.go +++ b/e2e/testconfig/testconfig.go @@ -44,7 +44,7 @@ const ( defaultBinary = "simd" // defaultRlyTag is the tag that will be used if no relayer tag is specified. // all images are here https://github.com/cosmos/relayer/pkgs/container/relayer/versions - defaultRlyTag = "andrew-tendermint_v0.37" // "v2.2.0" + defaultRlyTag = "latest" // "andrew-tendermint_v0.37" // "v2.2.0" // defaultChainTag is the tag that will be used for the chains if none is specified. defaultChainTag = "main" // defaultRelayerType is the default relayer that will be used if none is specified. diff --git a/e2e/tests/interchain_accounts/localhost_test.go b/e2e/tests/interchain_accounts/localhost_test.go new file mode 100644 index 00000000000..bb334d4e798 --- /dev/null +++ b/e2e/tests/interchain_accounts/localhost_test.go @@ -0,0 +1,197 @@ +package interchain_accounts + +import ( + "context" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/gogoproto/proto" + controllertypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v7/testing" + "github.com/stretchr/testify/suite" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testvalues" + "github.com/strangelove-ventures/interchaintest/v7" + "github.com/strangelove-ventures/interchaintest/v7/ibc" + test "github.com/strangelove-ventures/interchaintest/v7/testutil" +) + +func TestInterchainAccountsLocalhostTestSuite(t *testing.T) { + suite.Run(t, new(LocalhostInterchainAccountsTestSuite)) +} + +type LocalhostInterchainAccountsTestSuite struct { + testsuite.E2ETestSuite +} + +func (s *LocalhostInterchainAccountsTestSuite) TestInterchainAccounts_Localhost() { + t := s.T() + ctx := context.TODO() + + _, _ = s.SetupChainsRelayerAndChannel(ctx) + chainA, _ := s.GetChains() + + chainADenom := chainA.Config().Denom + + rlyWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + userAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + userBWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + + var ( + msgChanOpenInitRes channeltypes.MsgChannelOpenInitResponse + msgChanOpenTryRes channeltypes.MsgChannelOpenTryResponse + ack []byte + packet channeltypes.Packet + ) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA), "failed to wait for blocks") + + version := icatypes.NewDefaultMetadataString(connectiontypes.LocalhostID, connectiontypes.LocalhostID) + controllerPortID, err := icatypes.NewControllerPortID(userAWallet.FormattedAddress()) + s.Require().NoError(err) + + t.Run("channel open init localhost - broadcast MsgRegisterInterchainAccount", func(t *testing.T) { + msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(connectiontypes.LocalhostID, userAWallet.FormattedAddress(), version) + + txResp, err := s.BroadcastMessages(ctx, chainA, userAWallet, msgRegisterAccount) + s.Require().NoError(err) + s.AssertValidTxResponse(txResp) + + s.Require().NoError(testsuite.UnmarshalMsgResponses(txResp, &msgChanOpenInitRes)) + }) + + t.Run("channel open try localhost", func(t *testing.T) { + msgChanOpenTry := channeltypes.NewMsgChannelOpenTry( + icatypes.HostPortID, icatypes.Version, + channeltypes.ORDERED, []string{connectiontypes.LocalhostID}, + controllerPortID, msgChanOpenInitRes.GetChannelId(), + version, nil, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp, err := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenTry) + s.Require().NoError(err) + s.AssertValidTxResponse(txResp) + + s.Require().NoError(testsuite.UnmarshalMsgResponses(txResp, &msgChanOpenTryRes)) + }) + + t.Run("channel open ack localhost", func(t *testing.T) { + msgChanOpenAck := channeltypes.NewMsgChannelOpenAck( + controllerPortID, msgChanOpenInitRes.GetChannelId(), + msgChanOpenTryRes.GetChannelId(), msgChanOpenTryRes.GetVersion(), + nil, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp, err := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenAck) + s.Require().NoError(err) + s.AssertValidTxResponse(txResp) + }) + + t.Run("channel open confirm localhost", func(t *testing.T) { + msgChanOpenConfirm := channeltypes.NewMsgChannelOpenConfirm( + icatypes.HostPortID, msgChanOpenTryRes.GetChannelId(), + nil, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp, err := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenConfirm) + s.Require().NoError(err) + s.AssertValidTxResponse(txResp) + }) + + t.Run("query localhost interchain accounts channel ends", func(t *testing.T) { + channelEndA, err := s.QueryChannel(ctx, chainA, controllerPortID, msgChanOpenInitRes.GetChannelId()) + s.Require().NoError(err) + s.Require().NotNil(channelEndA) + + channelEndB, err := s.QueryChannel(ctx, chainA, icatypes.HostPortID, msgChanOpenTryRes.GetChannelId()) + s.Require().NoError(err) + s.Require().NotNil(channelEndB) + + s.Require().Equal(channelEndA.GetConnectionHops(), channelEndB.GetConnectionHops()) + }) + + t.Run("verify interchain account registration and deposit funds", func(t *testing.T) { + interchainAccAddress, err := s.QueryInterchainAccount(ctx, chainA, userAWallet.FormattedAddress(), connectiontypes.LocalhostID) + s.Require().NoError(err) + s.Require().NotZero(len(interchainAccAddress)) + + walletAmount := ibc.WalletAmount{ + Address: interchainAccAddress, + Amount: testvalues.StartingTokenAmount, + Denom: chainADenom, + } + + s.Require().NoError(chainA.SendFunds(ctx, interchaintest.FaucetAccountKeyName, walletAmount)) + }) + + t.Run("send packet localhost interchain accounts", func(t *testing.T) { + interchainAccAddress, err := s.QueryInterchainAccount(ctx, chainA, userAWallet.FormattedAddress(), connectiontypes.LocalhostID) + s.Require().NoError(err) + s.Require().NotZero(len(interchainAccAddress)) + + msgSend := &banktypes.MsgSend{ + FromAddress: interchainAccAddress, + ToAddress: userBWallet.FormattedAddress(), + Amount: sdk.NewCoins(testvalues.DefaultTransferAmount(chainADenom)), + } + + 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(userAWallet.FormattedAddress(), connectiontypes.LocalhostID, uint64(time.Hour.Nanoseconds()), packetData) + + txResp, err := s.BroadcastMessages(ctx, chainA, userAWallet, msgSendTx) + s.AssertValidTxResponse(txResp) + s.Require().NoError(err) + + events := testsuite.ABCIToSDKEvents(txResp.Events) + packet, err = ibctesting.ParsePacketFromEvents(events) + s.Require().NoError(err) + s.Require().NotNil(packet) + }) + + t.Run("recv packet localhost interchain accounts", func(t *testing.T) { + msgRecvPacket := channeltypes.NewMsgRecvPacket(packet, nil, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress()) + + txResp, err := s.BroadcastMessages(ctx, chainA, rlyWallet, msgRecvPacket) + s.Require().NoError(err) + s.AssertValidTxResponse(txResp) + + events := testsuite.ABCIToSDKEvents(txResp.Events) + ack, err = ibctesting.ParseAckFromEvents(events) + s.Require().NoError(err) + s.Require().NotNil(ack) + }) + + t.Run("acknowledge packet localhost interchain accounts", func(t *testing.T) { + msgAcknowledgement := channeltypes.NewMsgAcknowledgement(packet, ack, nil, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress()) + + txResp, err := s.BroadcastMessages(ctx, chainA, rlyWallet, msgAcknowledgement) + s.Require().NoError(err) + s.AssertValidTxResponse(txResp) + }) + + t.Run("verify tokens transferred", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, controllerPortID, msgChanOpenInitRes.GetChannelId(), 1) + + balance, err := chainA.GetBalance(ctx, userBWallet.FormattedAddress(), chainADenom) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + testvalues.StartingTokenAmount + s.Require().Equal(expected, balance) + }) +} diff --git a/e2e/tests/transfer/localhost_test.go b/e2e/tests/transfer/localhost_test.go new file mode 100644 index 00000000000..3464c1eccd1 --- /dev/null +++ b/e2e/tests/transfer/localhost_test.go @@ -0,0 +1,169 @@ +package transfer + +import ( + "context" + "testing" + + "github.com/stretchr/testify/suite" + + transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v7/testing" + test "github.com/strangelove-ventures/interchaintest/v7/testutil" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testvalues" +) + +func TestTransferLocalhostTestSuite(t *testing.T) { + suite.Run(t, new(LocalhostTransferTestSuite)) +} + +type LocalhostTransferTestSuite struct { + testsuite.E2ETestSuite +} + +// TestMsgTransfer_Localhost creates two wallets on a single chain and performs MsgTransfers back and forth +// to ensure ibc functions as expected on localhost. This test is largely the same as TestMsgTransfer_Succeeds_Nonincentivized +// except that chain B is replaced with an additional wallet on chainA. +func (s *LocalhostTransferTestSuite) TestMsgTransfer_Localhost() { + t := s.T() + ctx := context.TODO() + + _, _ = s.SetupChainsRelayerAndChannel(ctx, transferChannelOptions()) + chainA, _ := s.GetChains() + + chainADenom := chainA.Config().Denom + + rlyWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + userAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + userBWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + + var ( + msgChanOpenInitRes channeltypes.MsgChannelOpenInitResponse + msgChanOpenTryRes channeltypes.MsgChannelOpenTryResponse + ack []byte + packet channeltypes.Packet + ) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA), "failed to wait for blocks") + + t.Run("channel open init localhost", func(t *testing.T) { + msgChanOpenInit := channeltypes.NewMsgChannelOpenInit( + transfertypes.PortID, transfertypes.Version, + channeltypes.UNORDERED, []string{connectiontypes.LocalhostID}, + transfertypes.PortID, rlyWallet.FormattedAddress(), + ) + + s.Require().NoError(msgChanOpenInit.ValidateBasic()) + + txResp, err := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenInit) + s.Require().NoError(err) + s.AssertValidTxResponse(txResp) + + s.Require().NoError(testsuite.UnmarshalMsgResponses(txResp, &msgChanOpenInitRes)) + }) + + t.Run("channel open try localhost", func(t *testing.T) { + msgChanOpenTry := channeltypes.NewMsgChannelOpenTry( + transfertypes.PortID, transfertypes.Version, + channeltypes.UNORDERED, []string{connectiontypes.LocalhostID}, + transfertypes.PortID, msgChanOpenInitRes.GetChannelId(), + transfertypes.Version, nil, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp, err := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenTry) + s.Require().NoError(err) + s.AssertValidTxResponse(txResp) + + s.Require().NoError(testsuite.UnmarshalMsgResponses(txResp, &msgChanOpenTryRes)) + }) + + t.Run("channel open ack localhost", func(t *testing.T) { + msgChanOpenAck := channeltypes.NewMsgChannelOpenAck( + transfertypes.PortID, msgChanOpenInitRes.GetChannelId(), + msgChanOpenTryRes.GetChannelId(), transfertypes.Version, + nil, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp, err := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenAck) + s.Require().NoError(err) + s.AssertValidTxResponse(txResp) + }) + + t.Run("channel open confirm localhost", func(t *testing.T) { + msgChanOpenConfirm := channeltypes.NewMsgChannelOpenConfirm( + transfertypes.PortID, msgChanOpenTryRes.GetChannelId(), + nil, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp, err := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenConfirm) + s.Require().NoError(err) + s.AssertValidTxResponse(txResp) + }) + + t.Run("query localhost transfer channel ends", func(t *testing.T) { + channelEndA, err := s.QueryChannel(ctx, chainA, transfertypes.PortID, msgChanOpenInitRes.GetChannelId()) + s.Require().NoError(err) + s.Require().NotNil(channelEndA) + + channelEndB, err := s.QueryChannel(ctx, chainA, transfertypes.PortID, msgChanOpenTryRes.GetChannelId()) + s.Require().NoError(err) + s.Require().NotNil(channelEndB) + + s.Require().Equal(channelEndA.GetConnectionHops(), channelEndB.GetConnectionHops()) + }) + + t.Run("send packet localhost ibc transfer", func(t *testing.T) { + txResp, err := s.Transfer(ctx, chainA, userAWallet, transfertypes.PortID, msgChanOpenInitRes.GetChannelId(), testvalues.DefaultTransferAmount(chainADenom), userAWallet.FormattedAddress(), userBWallet.FormattedAddress(), clienttypes.NewHeight(1, 100), 0, "") + s.Require().NoError(err) + s.AssertValidTxResponse(txResp) + + events := testsuite.ABCIToSDKEvents(txResp.Events) + packet, err = ibctesting.ParsePacketFromEvents(events) + s.Require().NoError(err) + s.Require().NotNil(packet) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, userAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("recv packet localhost ibc transfer", func(t *testing.T) { + msgRecvPacket := channeltypes.NewMsgRecvPacket(packet, nil, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress()) + + txResp, err := s.BroadcastMessages(ctx, chainA, rlyWallet, msgRecvPacket) + s.Require().NoError(err) + s.AssertValidTxResponse(txResp) + + events := testsuite.ABCIToSDKEvents(txResp.Events) + ack, err = ibctesting.ParseAckFromEvents(events) + s.Require().NoError(err) + s.Require().NotNil(ack) + }) + + t.Run("acknowledge packet localhost ibc transfer", func(t *testing.T) { + msgAcknowledgement := channeltypes.NewMsgAcknowledgement(packet, ack, nil, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress()) + + txResp, err := s.BroadcastMessages(ctx, chainA, rlyWallet, msgAcknowledgement) + s.Require().NoError(err) + s.AssertValidTxResponse(txResp) + }) + + t.Run("verify tokens transferred", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, transfertypes.PortID, msgChanOpenInitRes.GetChannelId(), 1) + + ibcToken := testsuite.GetIBCToken(chainADenom, transfertypes.PortID, msgChanOpenTryRes.GetChannelId()) + actualBalance, err := chainA.GetBalance(ctx, userBWallet.FormattedAddress(), ibcToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) +} diff --git a/e2e/testsuite/codec.go b/e2e/testsuite/codec.go index 5ad4a78febe..267f08886a9 100644 --- a/e2e/testsuite/codec.go +++ b/e2e/testsuite/codec.go @@ -1,8 +1,12 @@ package testsuite import ( + "encoding/hex" + "fmt" + "github.com/cosmos/cosmos-sdk/codec" sdkcodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/x/authz" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" @@ -25,11 +29,13 @@ import ( simappparams "github.com/cosmos/ibc-go/v7/testing/simapp/params" ) +// Codec returns the global E2E protobuf codec. func Codec() *codec.ProtoCodec { cdc, _ := codecAndEncodingConfig() return cdc } +// EncodingConfig returns the global E2E encoding config. func EncodingConfig() simappparams.EncodingConfig { _, cfg := codecAndEncodingConfig() return cfg @@ -64,3 +70,29 @@ func codecAndEncodingConfig() (*codec.ProtoCodec, simappparams.EncodingConfig) { cdc := codec.NewProtoCodec(cfg.InterfaceRegistry) return cdc, cfg } + +// UnmarshalMsgResponses attempts to unmarshal the tx msg responses into the provided message types. +func UnmarshalMsgResponses(txResp sdk.TxResponse, msgs ...codec.ProtoMarshaler) error { + cdc := Codec() + bz, err := hex.DecodeString(txResp.Data) + if err != nil { + return err + } + + var txMsgData sdk.TxMsgData + if err := cdc.Unmarshal(bz, &txMsgData); err != nil { + return err + } + + if len(msgs) != len(txMsgData.MsgResponses) { + return fmt.Errorf("expected %d message responses but got %d", len(msgs), len(txMsgData.MsgResponses)) + } + + for i, msg := range msgs { + if err := cdc.Unmarshal(txMsgData.MsgResponses[i].Value, msg); err != nil { + return err + } + } + + return nil +} diff --git a/e2e/testsuite/events.go b/e2e/testsuite/events.go new file mode 100644 index 00000000000..feb79372123 --- /dev/null +++ b/e2e/testsuite/events.go @@ -0,0 +1,21 @@ +package testsuite + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +// ABCIToSDKEvents converts a list of ABCI events to Cosmos SDK events. +func ABCIToSDKEvents(abciEvents []abci.Event) sdk.Events { + var events sdk.Events + for _, evt := range abciEvents { + var attributes []sdk.Attribute + for _, attr := range evt.GetAttributes() { + attributes = append(attributes, sdk.NewAttribute(attr.Key, attr.Value)) + } + + events = events.AppendEvent(sdk.NewEvent(evt.GetType(), attributes...)) + } + + return events +} diff --git a/e2e/testsuite/testsuite.go b/e2e/testsuite/testsuite.go index a8c8fb01661..51b9e621711 100644 --- a/e2e/testsuite/testsuite.go +++ b/e2e/testsuite/testsuite.go @@ -2,7 +2,6 @@ package testsuite import ( "context" - "errors" "fmt" "strconv" "strings" @@ -544,7 +543,7 @@ func (s *E2ETestSuite) QueryModuleAccountAddress(ctx context.Context, moduleName } moduleAccount, ok := account.(authtypes.ModuleAccountI) if !ok { - return nil, errors.New(fmt.Sprintf("failed to cast account: %T as ModuleAccount", moduleAccount)) + return nil, fmt.Errorf("failed to cast account: %T as ModuleAccount", moduleAccount) } return moduleAccount.GetAddress(), nil