diff --git a/.github/workflows/e2e-compatibility-workflow-call.yaml b/.github/workflows/e2e-compatibility-workflow-call.yaml index b4db9c0e075..eb472d0eef0 100644 --- a/.github/workflows/e2e-compatibility-workflow-call.yaml +++ b/.github/workflows/e2e-compatibility-workflow-call.yaml @@ -50,4 +50,4 @@ jobs: CHAIN_A_TAG: "${{ matrix.chain-a }}" CHAIN_B_TAG: "${{ matrix.chain-b }}" CHAIN_BINARY: "${{ matrix.chain-binary }}" - RLY_TAG: "v2.0.0" + RLY_TAG: "v2.1.2" diff --git a/.github/workflows/e2e-fork.yml b/.github/workflows/e2e-fork.yml index ea14e3a6dd7..09462fb4e25 100644 --- a/.github/workflows/e2e-fork.yml +++ b/.github/workflows/e2e-fork.yml @@ -48,7 +48,7 @@ jobs: - name: Run e2e Test env: # see images here https://github.com/cosmos/relayer/pkgs/container/relayer/versions - RLY_TAG: "v2.0.0" + RLY_TAG: "v2.1.2" run: | cd e2e make e2e-test entrypoint=${{ matrix.entrypoint }} test=${{ matrix.test }} diff --git a/.github/workflows/e2e-manual-icad.yaml b/.github/workflows/e2e-manual-icad.yaml index 6f2a0f389f9..469f5baa8d7 100644 --- a/.github/workflows/e2e-manual-icad.yaml +++ b/.github/workflows/e2e-manual-icad.yaml @@ -45,7 +45,7 @@ on: relayer-tag: description: 'The tag to use for the relayer' required: true - default: "v2.0.0" + default: "v2.1.2" type: string diff --git a/.github/workflows/e2e-manual-simd.yaml b/.github/workflows/e2e-manual-simd.yaml index 7658b8f7024..6218eb2dc0f 100644 --- a/.github/workflows/e2e-manual-simd.yaml +++ b/.github/workflows/e2e-manual-simd.yaml @@ -15,6 +15,7 @@ on: - TestConnectionTestSuite - TestInterchainAccountsTestSuite - TestInterchainAccountsGroupsTestSuite + - TestInterchainAccountsGovTestSuite - TestIncentivizedInterchainAccountsTestSuite chain-image: description: 'The image to use for chain A' @@ -56,7 +57,7 @@ on: relayer-tag: description: 'The tag to use for the relayer' required: true - default: "v2.0.0" + default: "v2.1.2" type: string diff --git a/.github/workflows/e2e-test-workflow-call.yml b/.github/workflows/e2e-test-workflow-call.yml index 31713b30035..f5ac978d19a 100644 --- a/.github/workflows/e2e-test-workflow-call.yml +++ b/.github/workflows/e2e-test-workflow-call.yml @@ -34,7 +34,7 @@ on: relayer-tag: description: 'The tag to use for the relayer' required: true - default: "v2.0.0" + default: "v2.1.2" type: string build-and-push-docker-image: description: "Flag to specify if the docker image should be built and pushed beforehand" diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index ebd46c1357e..f2a96f5dce5 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -45,7 +45,7 @@ jobs: chain-image: ghcr.io/cosmos/ibc-go-simd chain-a-tag: "${{ needs.determine-image-tag.outputs.simd-tag }}" chain-b-tag: "${{ needs.determine-image-tag.outputs.simd-tag }}" - relayer-tag: "v2.0.0" + relayer-tag: "v2.1.2" chain-binary: "simd" # on regular PRs we won't run interchain account or upgrade tests. test-exclusions: "TestInterTxTestSuite,TestIncentivizedInterTxTestSuite,TestUpgradeTestSuite" diff --git a/e2e/scripts/run-e2e.sh b/e2e/scripts/run-e2e.sh index 56fb2b4da34..2703dd50e0d 100755 --- a/e2e/scripts/run-e2e.sh +++ b/e2e/scripts/run-e2e.sh @@ -6,14 +6,14 @@ ENTRY_POINT="${1}" TEST="${2}" export CHAIN_A_TAG="${CHAIN_A_TAG:-latest}" -export CHAIN_A_IMAGE="${CHAIN_A_IMAGE:-ibc-go-simd}" +export CHAIN_IMAGE="${CHAIN_IMAGE:-ibc-go-simd}" export CHAIN_BINARY="${CHAIN_BINARY:-simd}" # In CI, the docker images will be built separately. # context for building the image is one directory up. if [ "${CI:-}" != "true" ] then - (cd ..; docker build . -t "${CHAIN_A_IMAGE}:${CHAIN_A_TAG}") + (cd ..; docker build . -t "${CHAIN_IMAGE}:${CHAIN_A_TAG}") fi go test -v ./tests/... --run ${ENTRY_POINT} -testify.m ^${TEST}$ diff --git a/e2e/testconfig/testconfig.go b/e2e/testconfig/testconfig.go index 926d6dbb92f..524d5c417ee 100644 --- a/e2e/testconfig/testconfig.go +++ b/e2e/testconfig/testconfig.go @@ -37,6 +37,8 @@ const ( defaultBinary = "simd" // defaultRlyTag is the tag that will be used if no relayer tag is specified. defaultRlyTag = "main" + // defaultChainTag is the tag that will be used for the chains if none is specified. + defaultChainTag = "main" ) func getChainImage(binary string) string { @@ -69,7 +71,7 @@ func FromEnv() TestConfig { chainATag, ok := os.LookupEnv(ChainATagEnv) if !ok { - panic(fmt.Sprintf("must specify %s version for test with environment variable [%s]", chainBinary, ChainATagEnv)) + chainATag = defaultChainTag } chainBTag, ok := os.LookupEnv(ChainBTagEnv) diff --git a/e2e/tests/interchain_accounts/base_test.go b/e2e/tests/interchain_accounts/base_test.go index f34b8a86622..db838423b49 100644 --- a/e2e/tests/interchain_accounts/base_test.go +++ b/e2e/tests/interchain_accounts/base_test.go @@ -19,7 +19,6 @@ import ( "github.com/cosmos/ibc-go/e2e/testconfig" "github.com/cosmos/ibc-go/e2e/testsuite" "github.com/cosmos/ibc-go/e2e/testvalues" - 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" diff --git a/e2e/tests/interchain_accounts/gov_test.go b/e2e/tests/interchain_accounts/gov_test.go new file mode 100644 index 00000000000..f0b18602bc3 --- /dev/null +++ b/e2e/tests/interchain_accounts/gov_test.go @@ -0,0 +1,116 @@ +package interchain_accounts + +import ( + "context" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/gogo/protobuf/proto" + "github.com/strangelove-ventures/ibctest/v6" + "github.com/strangelove-ventures/ibctest/v6/ibc" + "github.com/strangelove-ventures/ibctest/v6/test" + "github.com/stretchr/testify/suite" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testvalues" + 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" + ibctesting "github.com/cosmos/ibc-go/v6/testing" +) + +func TestInterchainAccountsGovTestSuite(t *testing.T) { + suite.Run(t, new(InterchainAccountsGovTestSuite)) +} + +type InterchainAccountsGovTestSuite struct { + testsuite.E2ETestSuite +} + +func (s *InterchainAccountsGovTestSuite) TestInterchainAccountsGovIntegration() { + 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() + controllerAccount := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + + chainBAccount := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBAccount.Bech32Address(chainB.Config().Bech32Prefix) + + govModuleAddress, err := s.QueryModuleAccountAddress(ctx, govtypes.ModuleName, chainA) + s.Require().NoError(err) + s.Require().NotNil(govModuleAddress) + + t.Run("execute proposal for MsgRegisterInterchainAccount", func(t *testing.T) { + version := icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) + msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, govModuleAddress.String(), version) + s.ExecuteGovProposalV1(ctx, msgRegisterAccount, chainA, controllerAccount, 1) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB)) + + var interchainAccAddr string + t.Run("verify interchain account registration success", func(t *testing.T) { + var err error + interchainAccAddr, err = s.QueryInterchainAccount(ctx, chainA, govModuleAddress.String(), ibctesting.FirstConnectionID) + s.Require().NoError(err) + s.Require().NotZero(len(interchainAccAddr)) + + 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, so it has some $$ to send + err := chainB.SendFunds(ctx, ibctest.FaucetAccountKeyName, ibc.WalletAmount{ + Address: interchainAccAddr, + Amount: testvalues.StartingTokenAmount, + Denom: chainB.Config().Denom, + }) + s.Require().NoError(err) + }) + + t.Run("execute proposal for MsgSendTx", func(t *testing.T) { + msgBankSend := &banktypes.MsgSend{ + FromAddress: interchainAccAddr, + ToAddress: chainBAddress, + Amount: sdk.NewCoins(testvalues.DefaultTransferAmount(chainB.Config().Denom)), + } + + cdc := testsuite.Codec() + bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{msgBankSend}) + s.Require().NoError(err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "e2e", + } + + msgSendTx := controllertypes.NewMsgSendTx(govModuleAddress.String(), ibctesting.FirstConnectionID, uint64(time.Hour.Nanoseconds()), packetData) + s.ExecuteGovProposalV1(ctx, msgSendTx, chainA, controllerAccount, 2) + }) + + 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, interchainAccAddr, chainB.Config().Denom) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + testvalues.StartingTokenAmount + s.Require().Equal(expected, balance) + }) + }) +} diff --git a/e2e/testsuite/codec.go b/e2e/testsuite/codec.go index 4998de66f7a..dcee96ced4f 100644 --- a/e2e/testsuite/codec.go +++ b/e2e/testsuite/codec.go @@ -2,6 +2,7 @@ package testsuite import ( "github.com/cosmos/cosmos-sdk/codec" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" @@ -9,9 +10,20 @@ import ( ) func Codec() *codec.ProtoCodec { + cdc, _ := codecAndEncodingConfig() + return cdc +} + +func EncodingConfig() simappparams.EncodingConfig { + _, cfg := codecAndEncodingConfig() + return cfg +} + +func codecAndEncodingConfig() (*codec.ProtoCodec, simappparams.EncodingConfig) { cfg := simappparams.MakeTestEncodingConfig() banktypes.RegisterInterfaces(cfg.InterfaceRegistry) govv1beta1.RegisterInterfaces(cfg.InterfaceRegistry) + authtypes.RegisterInterfaces(cfg.InterfaceRegistry) cdc := codec.NewProtoCodec(cfg.InterfaceRegistry) - return cdc + return cdc, cfg } diff --git a/e2e/testsuite/grpc_query.go b/e2e/testsuite/grpc_query.go index 7696f630b4a..214dd355340 100644 --- a/e2e/testsuite/grpc_query.go +++ b/e2e/testsuite/grpc_query.go @@ -3,7 +3,8 @@ package testsuite import ( "context" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + govtypesbeta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" intertxtypes "github.com/cosmos/interchain-accounts/x/inter-tx/types" "github.com/strangelove-ventures/ibctest/v6/chain/cosmos" "github.com/strangelove-ventures/ibctest/v6/ibc" @@ -120,14 +121,26 @@ func (s *E2ETestSuite) QueryCounterPartyPayee(ctx context.Context, chain ibc.Cha } // QueryProposal queries the governance proposal on the given chain with the given proposal ID. -func (s *E2ETestSuite) QueryProposal(ctx context.Context, chain ibc.Chain, proposalID uint64) (govtypes.Proposal, error) { +func (s *E2ETestSuite) QueryProposal(ctx context.Context, chain ibc.Chain, proposalID uint64) (govtypesbeta1.Proposal, error) { queryClient := s.GetChainGRCPClients(chain).GovQueryClient - res, err := queryClient.Proposal(ctx, &govtypes.QueryProposalRequest{ + res, err := queryClient.Proposal(ctx, &govtypesbeta1.QueryProposalRequest{ ProposalId: proposalID, }) if err != nil { - return govtypes.Proposal{}, err + return govtypesbeta1.Proposal{}, err } return res.Proposal, nil } + +func (s *E2ETestSuite) QueryProposalV1(ctx context.Context, chain ibc.Chain, proposalID uint64) (govtypesv1.Proposal, error) { + queryClient := s.GetChainGRCPClients(chain).GovQueryClientV1 + res, err := queryClient.Proposal(ctx, &govtypesv1.QueryProposalRequest{ + ProposalId: proposalID, + }) + if err != nil { + return govtypesv1.Proposal{}, err + } + + return *res.Proposal, nil +} diff --git a/e2e/testsuite/testsuite.go b/e2e/testsuite/testsuite.go index dae0182f1cd..f628f6154ce 100644 --- a/e2e/testsuite/testsuite.go +++ b/e2e/testsuite/testsuite.go @@ -2,13 +2,17 @@ package testsuite import ( "context" + "errors" "fmt" + "strconv" "strings" "time" "github.com/cosmos/cosmos-sdk/client/tx" sdk "github.com/cosmos/cosmos-sdk/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + govtypesv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" grouptypes "github.com/cosmos/cosmos-sdk/x/group" paramsproposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" intertxtypes "github.com/cosmos/interchain-accounts/x/inter-tx/types" @@ -40,7 +44,6 @@ const ( ChainBRelayerName = "rlyB" // DefaultGasValue is the default gas value used to configure tx.Factory DefaultGasValue = 500000 - // emptyLogs is the string value returned from `BroadcastMessages`. There are some situations in which // the result is empty, when this happens we include the raw logs instead to get as much information // amount the failure as possible. @@ -73,9 +76,11 @@ type GRPCClients struct { InterTxQueryClient intertxtypes.QueryClient // SDK query clients - GovQueryClient govtypes.QueryClient + GovQueryClient govtypesv1beta1.QueryClient + GovQueryClientV1 govtypesv1.QueryClient GroupsQueryClient grouptypes.QueryClient ParamsQueryClient paramsproposaltypes.QueryClient + AuthQueryClient authtypes.QueryClient } // path is a pairing of two chains which will be used in a test. @@ -381,9 +386,11 @@ func (s *E2ETestSuite) initGRPCClients(chain *cosmos.CosmosChain) { FeeQueryClient: feetypes.NewQueryClient(grpcConn), ICAQueryClient: controllertypes.NewQueryClient(grpcConn), InterTxQueryClient: intertxtypes.NewQueryClient(grpcConn), - GovQueryClient: govtypes.NewQueryClient(grpcConn), + GovQueryClient: govtypesv1beta1.NewQueryClient(grpcConn), + GovQueryClientV1: govtypesv1.NewQueryClient(grpcConn), GroupsQueryClient: grouptypes.NewQueryClient(grpcConn), ParamsQueryClient: paramsproposaltypes.NewQueryClient(grpcConn), + AuthQueryClient: authtypes.NewQueryClient(grpcConn), } } @@ -450,12 +457,12 @@ func GetNativeChainBalance(ctx context.Context, chain ibc.Chain, user *ibc.Walle } // ExecuteGovProposal submits the given governance proposal using the provided user and uses all validators to vote yes on the proposal. -// It ensure the proposal successfully passes. -func (s *E2ETestSuite) ExecuteGovProposal(ctx context.Context, chain *cosmos.CosmosChain, user *ibc.Wallet, content govtypes.Content) { +// It ensures the proposal successfully passes. +func (s *E2ETestSuite) ExecuteGovProposal(ctx context.Context, chain *cosmos.CosmosChain, user *ibc.Wallet, content govtypesv1beta1.Content) { sender, err := sdk.AccAddressFromBech32(user.Bech32Address(chain.Config().Bech32Prefix)) s.Require().NoError(err) - msgSubmitProposal, err := govtypes.NewMsgSubmitProposal(content, sdk.NewCoins(sdk.NewCoin(chain.Config().Denom, govtypes.DefaultMinDepositTokens)), sender) + msgSubmitProposal, err := govtypesv1beta1.NewMsgSubmitProposal(content, sdk.NewCoins(sdk.NewCoin(chain.Config().Denom, govtypesv1beta1.DefaultMinDepositTokens)), sender) s.Require().NoError(err) txResp, err := s.BroadcastMessages(ctx, chain, user, msgSubmitProposal) @@ -467,7 +474,7 @@ func (s *E2ETestSuite) ExecuteGovProposal(ctx context.Context, chain *cosmos.Cos proposal, err := s.QueryProposal(ctx, chain, 1) s.Require().NoError(err) - s.Require().Equal(govtypes.StatusVotingPeriod, proposal.Status) + s.Require().Equal(govtypesv1beta1.StatusVotingPeriod, proposal.Status) err = chain.VoteOnProposalAllValidators(ctx, "1", cosmos.ProposalVoteYes) s.Require().NoError(err) @@ -475,13 +482,61 @@ func (s *E2ETestSuite) ExecuteGovProposal(ctx context.Context, chain *cosmos.Cos // ensure voting period has not passed before validators finished voting proposal, err = s.QueryProposal(ctx, chain, 1) s.Require().NoError(err) - s.Require().Equal(govtypes.StatusVotingPeriod, proposal.Status) + s.Require().Equal(govtypesv1beta1.StatusVotingPeriod, proposal.Status) time.Sleep(testvalues.VotingPeriod) // pass proposal proposal, err = s.QueryProposal(ctx, chain, 1) s.Require().NoError(err) - s.Require().Equal(govtypes.StatusPassed, proposal.Status) + s.Require().Equal(govtypesv1beta1.StatusPassed, proposal.Status) +} + +// ExecuteGovProposalV1 submits a governance proposal using the provided user and message and uses all validators +// to vote yes on the proposal. It ensures the proposal successfully passes. +func (s *E2ETestSuite) ExecuteGovProposalV1(ctx context.Context, msg sdk.Msg, chain *cosmos.CosmosChain, user *ibc.Wallet, proposalID uint64) { + sender, err := sdk.AccAddressFromBech32(user.Bech32Address(chain.Config().Bech32Prefix)) + s.Require().NoError(err) + + msgs := []sdk.Msg{msg} + msgSubmitProposal, err := govtypesv1.NewMsgSubmitProposal(msgs, sdk.NewCoins(sdk.NewCoin(chain.Config().Denom, govtypesv1.DefaultMinDepositTokens)), sender.String(), "") + s.Require().NoError(err) + + resp, err := s.BroadcastMessages(ctx, chain, user, msgSubmitProposal) + s.AssertValidTxResponse(resp) + s.Require().NoError(err) + + s.Require().NoError(chain.VoteOnProposalAllValidators(ctx, strconv.Itoa(int(proposalID)), cosmos.ProposalVoteYes)) + + time.Sleep(testvalues.VotingPeriod) + + proposal, err := s.QueryProposalV1(ctx, chain, proposalID) + s.Require().NoError(err) + s.Require().Equal(govtypesv1.StatusPassed, proposal.Status) +} + +// QueryModuleAccountAddress returns the sdk.AccAddress of a given module name. +func (s *E2ETestSuite) QueryModuleAccountAddress(ctx context.Context, moduleName string, chain *cosmos.CosmosChain) (sdk.AccAddress, error) { + authClient := s.GetChainGRCPClients(chain).AuthQueryClient + + resp, err := authClient.ModuleAccountByName(ctx, &authtypes.QueryModuleAccountByNameRequest{ + Name: moduleName, + }) + if err != nil { + return nil, err + } + + cfg := EncodingConfig() + + var account authtypes.AccountI + if err := cfg.InterfaceRegistry.UnpackAny(resp.Account, &account); err != nil { + return nil, err + } + moduleAccount, ok := account.(authtypes.ModuleAccountI) + if !ok { + return nil, errors.New(fmt.Sprintf("failed to cast account: %T as ModuleAccount", moduleAccount)) + } + + return moduleAccount.GetAddress(), nil } // GetIBCToken returns the denomination of the full token denom sent to the receiving channel