From 1877c8292b262bb5d401e0b90663375f24633dac Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 5 Dec 2023 12:59:50 +0100 Subject: [PATCH 1/3] fix: remove 08-wasm from 02-client exported (#5306) * fix: remove 08-wasm from 02-client exported * fix grandpa test * add function to add 08-wasm to allowed clients * e2e: add gov proposal to add 08-wasm client to list of allowed clients * use cosmos chain when querying gov module address * make functions for querying allowed clients and to add 08-wasm * Update modules/light-clients/08-wasm/types/validation.go Co-authored-by: DimitrisJim * Update modules/light-clients/08-wasm/types/keys.go Co-authored-by: Damian Nolan * Update keys.go --------- Co-authored-by: DimitrisJim Co-authored-by: Damian Nolan (cherry picked from commit e3ab9beca9b2edfc4e682ff93e0bcf8e5de39582) # Conflicts: # e2e/tests/wasm/grandpa_test.go # modules/core/02-client/types/params.go # modules/light-clients/08-wasm/types/client_message_test.go # modules/light-clients/08-wasm/types/consensus_state_test.go # modules/light-clients/08-wasm/types/keys.go # modules/light-clients/08-wasm/types/validation.go # modules/light-clients/08-wasm/types/validation_test.go --- e2e/tests/wasm/grandpa_test.go | 832 ++++++++++++++++++ modules/core/02-client/types/params.go | 5 + modules/core/exported/client.go | 3 - .../08-wasm/keeper/keeper_test.go | 2 + .../08-wasm/testing/wasm_endpoint.go | 9 + .../08-wasm/types/client_message.go | 2 +- .../08-wasm/types/client_message_test.go | 5 +- .../08-wasm/types/client_state.go | 2 +- .../08-wasm/types/consensus_state.go | 2 +- .../08-wasm/types/consensus_state_test.go | 5 +- modules/light-clients/08-wasm/types/keys.go | 5 + .../light-clients/08-wasm/types/types_test.go | 2 + .../light-clients/08-wasm/types/validation.go | 9 +- .../08-wasm/types/validation_test.go | 7 +- 14 files changed, 879 insertions(+), 11 deletions(-) create mode 100644 e2e/tests/wasm/grandpa_test.go diff --git a/e2e/tests/wasm/grandpa_test.go b/e2e/tests/wasm/grandpa_test.go new file mode 100644 index 00000000000..6aa250b1885 --- /dev/null +++ b/e2e/tests/wasm/grandpa_test.go @@ -0,0 +1,832 @@ +//go:build !test_e2e + +package wasm + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "os" + "testing" + "time" + + "github.com/strangelove-ventures/interchaintest/v8" + "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" + "github.com/strangelove-ventures/interchaintest/v8/chain/polkadot" + "github.com/strangelove-ventures/interchaintest/v8/ibc" + "github.com/strangelove-ventures/interchaintest/v8/testutil" + testifysuite "github.com/stretchr/testify/suite" + + cmtjson "github.com/cometbft/cometbft/libs/json" + + "cosmossdk.io/math" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + paramsproposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testvalues" + wasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v8/testing" +) + +const ( + composable = "composable" + simd = "simd" + wasmSimdImage = "ghcr.io/cosmos/ibc-go-wasm-simd" + + defaultWasmClientID = "08-wasm-0" +) + +func TestGrandpaTestSuite(t *testing.T) { + // this test suite only works with the hyperspace relayer, for now hard code this here. + // this will enforce that the hyperspace relayer is used in CI. + t.Setenv(testsuite.RelayerIDEnv, "hyperspace") + + // TODO: this value should be passed in via the config file / CI, not hard coded in the test. + // This configuration can be handled in https://github.com/cosmos/ibc-go/issues/4697 + if testsuite.IsCI() && !testsuite.IsFork() { + t.Setenv(testsuite.ChainImageEnv, wasmSimdImage) + } + + // wasm tests require a longer voting period to account for the time it takes to upload a contract. + testvalues.VotingPeriod = time.Minute * 5 + + validateTestConfig() + testifysuite.Run(t, new(GrandpaTestSuite)) +} + +type GrandpaTestSuite struct { + testsuite.E2ETestSuite +} + +// TestMsgTransfer_Succeeds_GrandpaContract features +// * sets up a Polkadot parachain +// * sets up a Cosmos chain +// * sets up the Hyperspace relayer +// * Funds a user wallet on both chains +// * Pushes a wasm client contract to the Cosmos chain +// * create client, connection, and channel in relayer +// * start relayer +// * send transfer over ibc +func (s *GrandpaTestSuite) TestMsgTransfer_Succeeds_GrandpaContract() { + ctx := context.Background() + t := s.T() + + chainA, chainB := s.GetGrandpaTestChains() + + polkadotChain := chainA.(*polkadot.PolkadotChain) + cosmosChain := chainB.(*cosmos.CosmosChain) + + // we explicitly skip path creation as the contract needs to be uploaded before we can create clients. + r := s.ConfigureRelayer(ctx, polkadotChain, cosmosChain, nil, func(options *interchaintest.InterchainBuildOptions) { + options.SkipPathCreation = true + }) + + s.InitGRPCClients(cosmosChain) + + cosmosWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + + file, err := os.Open("contracts/ics10_grandpa_cw.wasm.gz") + s.Require().NoError(err) + + checksum := s.PushNewWasmClientProposal(ctx, cosmosChain, cosmosWallet, file) + + s.Require().NotEmpty(checksum, "checksum was empty but should not have been") + + eRep := s.GetRelayerExecReporter() + + // Set client contract hash in cosmos chain config + err = r.SetClientContractHash(ctx, eRep, cosmosChain.Config(), checksum) + s.Require().NoError(err) + + // Ensure parachain has started (starts 1 session/epoch after relay chain) + err = testutil.WaitForBlocks(ctx, 1, polkadotChain) + s.Require().NoError(err, "polkadot chain failed to make blocks") + + // Fund users on both cosmos and parachain, mints Asset 1 for Alice + fundAmount := int64(12_333_000_000_000) + polkadotUser, cosmosUser := s.fundUsers(ctx, fundAmount, polkadotChain, cosmosChain) + + // TODO: this can be refactored to broadcast a MsgTransfer instead of CLI. + // https://github.com/cosmos/ibc-go/issues/4963 + amountToSend := int64(1_770_000) + transfer := ibc.WalletAmount{ + Address: polkadotUser.FormattedAddress(), + Denom: cosmosChain.Config().Denom, + Amount: math.NewInt(amountToSend), + } + + pathName := s.GetPathName(0) + + err = r.GeneratePath(ctx, eRep, cosmosChain.Config().ChainID, polkadotChain.Config().ChainID, pathName) + s.Require().NoError(err) + + t.Run("add 08-wasm to list of allowed clients", func(t *testing.T) { + allowedClients := s.queryAllowedClientsParam(ctx, cosmosChain) + s.allowWasmClients(ctx, cosmosChain, cosmosWallet, allowedClients) + }) + + // Create new clients + err = r.CreateClients(ctx, eRep, pathName, ibc.DefaultClientOpts()) + s.Require().NoError(err) + err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) // these 1 block waits seem to be needed to reduce flakiness + s.Require().NoError(err) + + // Create a new connection + err = r.CreateConnections(ctx, eRep, pathName) + s.Require().NoError(err) + err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) + s.Require().NoError(err) + + // Create a new channel & get channels from each chain + err = r.CreateChannel(ctx, eRep, pathName, ibc.DefaultChannelOpts()) + s.Require().NoError(err) + err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) + s.Require().NoError(err) + + // Start relayer + s.Require().NoError(r.StartRelayer(ctx, eRep, pathName)) + + t.Run("send successful IBC transfer from Cosmos to Polkadot parachain", func(t *testing.T) { + // Send 1.77 stake from cosmosUser to parachainUser + tx, err := cosmosChain.SendIBCTransfer(ctx, "channel-0", cosmosUser.KeyName(), transfer, ibc.TransferOptions{}) + s.Require().NoError(tx.Validate(), "source ibc transfer tx is invalid") + s.Require().NoError(err) + // verify token balance for cosmos user has decreased + balance, err := cosmosChain.GetBalance(ctx, cosmosUser.FormattedAddress(), cosmosChain.Config().Denom) + s.Require().NoError(err) + s.Require().Equal(balance, math.NewInt(fundAmount-amountToSend), "unexpected cosmos user balance after first tx") + err = testutil.WaitForBlocks(ctx, 15, cosmosChain, polkadotChain) + s.Require().NoError(err) + + // Verify tokens arrived on parachain user + parachainUserStake, err := polkadotChain.GetIbcBalance(ctx, string(polkadotUser.Address()), 2) + s.Require().NoError(err) + s.Require().Equal(amountToSend, parachainUserStake.Amount.Int64(), "unexpected parachain user balance after first tx") + }) + + t.Run("send two successful IBC transfers from Polkadot parachain to Cosmos, first with ibc denom, second with parachain denom", func(t *testing.T) { + // Send 1.16 stake from parachainUser to cosmosUser + amountToReflect := int64(1_160_000) + reflectTransfer := ibc.WalletAmount{ + Address: cosmosUser.FormattedAddress(), + Denom: "2", // stake + Amount: math.NewInt(amountToReflect), + } + _, err := polkadotChain.SendIBCTransfer(ctx, "channel-0", polkadotUser.KeyName(), reflectTransfer, ibc.TransferOptions{}) + s.Require().NoError(err) + + // Send 1.88 "UNIT" from Alice to cosmosUser + amountUnits := math.NewInt(1_880_000_000_000) + unitTransfer := ibc.WalletAmount{ + Address: cosmosUser.FormattedAddress(), + Denom: "1", // UNIT + Amount: amountUnits, + } + _, err = polkadotChain.SendIBCTransfer(ctx, "channel-0", "alice", unitTransfer, ibc.TransferOptions{}) + s.Require().NoError(err) + + // Wait for MsgRecvPacket on cosmos chain + finalStakeBal := math.NewInt(fundAmount - amountToSend + amountToReflect) + err = cosmos.PollForBalance(ctx, cosmosChain, 20, ibc.WalletAmount{ + Address: cosmosUser.FormattedAddress(), + Denom: cosmosChain.Config().Denom, + Amount: finalStakeBal, + }) + s.Require().NoError(err) + + // Wait for a new update state + err = testutil.WaitForBlocks(ctx, 5, cosmosChain, polkadotChain) + s.Require().NoError(err) + + // Verify cosmos user's final "stake" balance + cosmosUserStakeBal, err := cosmosChain.GetBalance(ctx, cosmosUser.FormattedAddress(), cosmosChain.Config().Denom) + s.Require().NoError(err) + s.Require().True(cosmosUserStakeBal.Equal(finalStakeBal)) + + // Verify cosmos user's final "unit" balance + unitDenomTrace := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", "channel-0", "UNIT")) + cosmosUserUnitBal, err := cosmosChain.GetBalance(ctx, cosmosUser.FormattedAddress(), unitDenomTrace.IBCDenom()) + s.Require().NoError(err) + s.Require().True(cosmosUserUnitBal.Equal(amountUnits)) + + // Verify parachain user's final "unit" balance (will be less than expected due gas costs for stake tx) + parachainUserUnits, err := polkadotChain.GetIbcBalance(ctx, string(polkadotUser.Address()), 1) + s.Require().NoError(err) + s.Require().True(parachainUserUnits.Amount.LTE(math.NewInt(fundAmount)), "parachain user's final unit amount not expected") + + // Verify parachain user's final "stake" balance + parachainUserStake, err := polkadotChain.GetIbcBalance(ctx, string(polkadotUser.Address()), 2) + s.Require().NoError(err) + s.Require().True(parachainUserStake.Amount.Equal(math.NewInt(amountToSend-amountToReflect)), "parachain user's final stake amount not expected") + }) +} + +// TestMsgTransfer_TimesOut_GrandpaContract +// sets up cosmos and polkadot chains, hyperspace relayer, and funds users on both chains +// * sends transfer over ibc channel, this transfer should timeout +func (s *GrandpaTestSuite) TestMsgTransfer_TimesOut_GrandpaContract() { + ctx := context.Background() + t := s.T() + + chainA, chainB := s.GetGrandpaTestChains() + + polkadotChain := chainA.(*polkadot.PolkadotChain) + cosmosChain := chainB.(*cosmos.CosmosChain) + + // we explicitly skip path creation as the contract needs to be uploaded before we can create clients. + r := s.ConfigureRelayer(ctx, polkadotChain, cosmosChain, nil, func(options *interchaintest.InterchainBuildOptions) { + options.SkipPathCreation = true + }) + + s.InitGRPCClients(cosmosChain) + + cosmosWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + + file, err := os.Open("contracts/ics10_grandpa_cw.wasm.gz") + s.Require().NoError(err) + + checksum := s.PushNewWasmClientProposal(ctx, cosmosChain, cosmosWallet, file) + + s.Require().NotEmpty(checksum, "checksum was empty but should not have been") + + eRep := s.GetRelayerExecReporter() + + // Set client contract hash in cosmos chain config + err = r.SetClientContractHash(ctx, eRep, cosmosChain.Config(), checksum) + s.Require().NoError(err) + + // Ensure parachain has started (starts 1 session/epoch after relay chain) + err = testutil.WaitForBlocks(ctx, 1, polkadotChain) + s.Require().NoError(err, "polkadot chain failed to make blocks") + + // Fund users on both cosmos and parachain, mints Asset 1 for Alice + fundAmount := int64(12_333_000_000_000) + polkadotUser, cosmosUser := s.fundUsers(ctx, fundAmount, polkadotChain, cosmosChain) + + // TODO: this can be refactored to broadcast a MsgTransfer instead of CLI. + // https://github.com/cosmos/ibc-go/issues/4963 + amountToSend := int64(1_770_000) + transfer := ibc.WalletAmount{ + Address: polkadotUser.FormattedAddress(), + Denom: cosmosChain.Config().Denom, + Amount: math.NewInt(amountToSend), + } + + pathName := s.GetPathName(0) + + err = r.GeneratePath(ctx, eRep, cosmosChain.Config().ChainID, polkadotChain.Config().ChainID, pathName) + s.Require().NoError(err) + + t.Run("add 08-wasm to list of allowed clients", func(t *testing.T) { + allowedClients := s.queryAllowedClientsParam(ctx, cosmosChain) + s.allowWasmClients(ctx, cosmosChain, cosmosWallet, allowedClients) + }) + + // Create new clients + err = r.CreateClients(ctx, eRep, pathName, ibc.DefaultClientOpts()) + s.Require().NoError(err) + err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) // these 1 block waits seem to be needed to reduce flakiness + s.Require().NoError(err) + + // Create a new connection + err = r.CreateConnections(ctx, eRep, pathName) + s.Require().NoError(err) + err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) + s.Require().NoError(err) + + // Create a new channel & get channels from each chain + err = r.CreateChannel(ctx, eRep, pathName, ibc.DefaultChannelOpts()) + s.Require().NoError(err) + err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) + s.Require().NoError(err) + + // Start relayer + s.Require().NoError(r.StartRelayer(ctx, eRep, pathName)) + + t.Run("IBC transfer from Cosmos chain to Polkadot parachain times out", func(t *testing.T) { + // Stop relayer + s.Require().NoError(r.StopRelayer(ctx, s.GetRelayerExecReporter())) + + tx, err := cosmosChain.SendIBCTransfer(ctx, "channel-0", cosmosUser.KeyName(), transfer, ibc.TransferOptions{Timeout: testvalues.ImmediatelyTimeout()}) + s.Require().NoError(err) + s.Require().NoError(tx.Validate(), "source ibc transfer tx is invalid") + time.Sleep(time.Nanosecond * 1) // want it to timeout immediately + + // check that tokens are escrowed + actualBalance, err := cosmosChain.GetBalance(ctx, cosmosUser.FormattedAddress(), cosmosChain.Config().Denom) + s.Require().NoError(err) + expected := fundAmount - amountToSend + s.Require().Equal(expected, actualBalance.Int64()) + + // start relayer + s.Require().NoError(r.StartRelayer(ctx, s.GetRelayerExecReporter(), s.GetPathName(0))) + err = testutil.WaitForBlocks(ctx, 15, polkadotChain, cosmosChain) + s.Require().NoError(err) + + // ensure that receiver on parachain did not receive any tokens + receiverBalance, err := polkadotChain.GetIbcBalance(ctx, polkadotUser.FormattedAddress(), 2) + s.Require().NoError(err) + s.Require().Equal(int64(0), receiverBalance.Amount.Int64()) + + // check that tokens have been refunded to sender address + senderBalance, err := cosmosChain.GetBalance(ctx, cosmosUser.FormattedAddress(), cosmosChain.Config().Denom) + s.Require().NoError(err) + s.Require().Equal(fundAmount, senderBalance.Int64()) + }) +} + +// TestMsgMigrateContract_Success_GrandpaContract features +// * sets up a Polkadot parachain +// * sets up a Cosmos chain +// * sets up the Hyperspace relayer +// * Funds a user wallet on both chains +// * Pushes a wasm client contract to the Cosmos chain +// * create client in relayer +// * Pushes a new wasm client contract to the Cosmos chain +// * Migrates the wasm client contract +func (s *GrandpaTestSuite) TestMsgMigrateContract_Success_GrandpaContract() { + ctx := context.Background() + t := s.T() + + chainA, chainB := s.GetGrandpaTestChains() + + polkadotChain := chainA.(*polkadot.PolkadotChain) + cosmosChain := chainB.(*cosmos.CosmosChain) + + // we explicitly skip path creation as the contract needs to be uploaded before we can create clients. + r := s.ConfigureRelayer(ctx, polkadotChain, cosmosChain, nil, func(options *interchaintest.InterchainBuildOptions) { + options.SkipPathCreation = true + }) + + s.InitGRPCClients(cosmosChain) + + cosmosWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + + file, err := os.Open("contracts/ics10_grandpa_cw.wasm.gz") + s.Require().NoError(err) + + checksum := s.PushNewWasmClientProposal(ctx, cosmosChain, cosmosWallet, file) + + s.Require().NotEmpty(checksum, "checksum was empty but should not have been") + + eRep := s.GetRelayerExecReporter() + + // Set client contract hash in cosmos chain config + err = r.SetClientContractHash(ctx, eRep, cosmosChain.Config(), checksum) + s.Require().NoError(err) + + // Ensure parachain has started (starts 1 session/epoch after relay chain) + err = testutil.WaitForBlocks(ctx, 1, polkadotChain) + s.Require().NoError(err, "polkadot chain failed to make blocks") + + pathName := s.GetPathName(0) + + err = r.GeneratePath(ctx, eRep, cosmosChain.Config().ChainID, polkadotChain.Config().ChainID, pathName) + s.Require().NoError(err) + + t.Run("add 08-wasm to list of allowed clients", func(t *testing.T) { + allowedClients := s.queryAllowedClientsParam(ctx, cosmosChain) + s.allowWasmClients(ctx, cosmosChain, cosmosWallet, allowedClients) + }) + + // Create new clients + err = r.CreateClients(ctx, eRep, pathName, ibc.DefaultClientOpts()) + s.Require().NoError(err) + err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) // these 1 block waits seem to be needed to reduce flakiness + s.Require().NoError(err) + + // Do not start relayer + + // This contract is a dummy contract that will always succeed migration. + // Other entry points are unimplemented. + migrateFile, err := os.Open("contracts/migrate_success.wasm.gz") + s.Require().NoError(err) + + // First Store the code + newChecksum := s.PushNewWasmClientProposal(ctx, cosmosChain, cosmosWallet, migrateFile) + s.Require().NotEmpty(newChecksum, "checksum was empty but should not have been") + + newChecksumBz, err := hex.DecodeString(newChecksum) + s.Require().NoError(err) + + // Attempt to migrate the contract + message := wasmtypes.NewMsgMigrateContract( + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + defaultWasmClientID, + newChecksumBz, + []byte("{}"), + ) + + s.ExecuteAndPassGovV1Proposal(ctx, message, cosmosChain, cosmosWallet) + + clientState, err := s.QueryClientState(ctx, cosmosChain, defaultWasmClientID) + s.Require().NoError(err) + + wasmClientState, ok := clientState.(*wasmtypes.ClientState) + s.Require().True(ok) + + s.Require().Equal(newChecksumBz, wasmClientState.Checksum) +} + +// TestMsgMigrateContract_ContractError_GrandpaContract features +// * sets up a Polkadot parachain +// * sets up a Cosmos chain +// * sets up the Hyperspace relayer +// * Funds a user wallet on both chains +// * Pushes a wasm client contract to the Cosmos chain +// * create client in relayer +// * Pushes a new wasm client contract to the Cosmos chain +// * Migrates the wasm client contract with a contract that will always fail migration +func (s *GrandpaTestSuite) TestMsgMigrateContract_ContractError_GrandpaContract() { + ctx := context.Background() + t := s.T() + + chainA, chainB := s.GetGrandpaTestChains() + + polkadotChain := chainA.(*polkadot.PolkadotChain) + cosmosChain := chainB.(*cosmos.CosmosChain) + + // we explicitly skip path creation as the contract needs to be uploaded before we can create clients. + r := s.ConfigureRelayer(ctx, polkadotChain, cosmosChain, nil, func(options *interchaintest.InterchainBuildOptions) { + options.SkipPathCreation = true + }) + + s.InitGRPCClients(cosmosChain) + + cosmosWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + + file, err := os.Open("contracts/ics10_grandpa_cw.wasm.gz") + s.Require().NoError(err) + checksum := s.PushNewWasmClientProposal(ctx, cosmosChain, cosmosWallet, file) + + s.Require().NotEmpty(checksum, "checksum was empty but should not have been") + + eRep := s.GetRelayerExecReporter() + + // Set client contract hash in cosmos chain config + err = r.SetClientContractHash(ctx, eRep, cosmosChain.Config(), checksum) + s.Require().NoError(err) + + // Ensure parachain has started (starts 1 session/epoch after relay chain) + err = testutil.WaitForBlocks(ctx, 1, polkadotChain) + s.Require().NoError(err, "polkadot chain failed to make blocks") + + pathName := s.GetPathName(0) + + err = r.GeneratePath(ctx, eRep, cosmosChain.Config().ChainID, polkadotChain.Config().ChainID, pathName) + s.Require().NoError(err) + + t.Run("add 08-wasm to list of allowed clients", func(t *testing.T) { + allowedClients := s.queryAllowedClientsParam(ctx, cosmosChain) + s.allowWasmClients(ctx, cosmosChain, cosmosWallet, allowedClients) + }) + + // Create new clients + err = r.CreateClients(ctx, eRep, pathName, ibc.DefaultClientOpts()) + s.Require().NoError(err) + err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) // these 1 block waits seem to be needed to reduce flakiness + s.Require().NoError(err) + + // Do not start the relayer + + // This contract is a dummy contract that will always fail migration. + // Other entry points are unimplemented. + migrateFile, err := os.Open("contracts/migrate_error.wasm.gz") + s.Require().NoError(err) + + // First Store the code + newChecksum := s.PushNewWasmClientProposal(ctx, cosmosChain, cosmosWallet, migrateFile) + s.Require().NotEmpty(newChecksum, "checksum was empty but should not have been") + + newChecksumBz, err := hex.DecodeString(newChecksum) + s.Require().NoError(err) + + // Attempt to migrate the contract + message := wasmtypes.NewMsgMigrateContract( + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + defaultWasmClientID, + newChecksumBz, + []byte("{}"), + ) + + err = s.ExecuteGovV1Proposal(ctx, message, cosmosChain, cosmosWallet) + // This is the error string that is returned from the contract + s.Require().ErrorContains(err, "migration not supported") +} + +// TestRecoverClient_Succeeds_GrandpaContract features: +// * setup cosmos and polkadot substrates nodes +// * funds test user wallets on both chains +// * stores a wasm client contract on the cosmos chain +// * creates a subject client using the hyperspace relayer +// * waits the expiry period and asserts the subject client status has expired +// * creates a substitute client using the hyperspace relayer +// * executes a gov proposal to recover the expired client +// * asserts the status of the subject client has been restored to active +// NOTE: The testcase features a modified grandpa client contract compiled as: +// - ics10_grandpa_cw_expiry.wasm.gz +// This contract modifies the unbonding period to 1600s with the trusting period being calculated as (unbonding period / 3). +func (s *GrandpaTestSuite) TestRecoverClient_Succeeds_GrandpaContract() { + ctx := context.Background() + t := s.T() + + // set the trusting period to a value which will still be valid upon client creation, but invalid before the first update + // the contract uses 1600s as the unbonding period with the trusting period evaluating to (unbonding period / 3) + modifiedTrustingPeriod := (1600 * time.Second) / 3 + + chainA, chainB := s.GetGrandpaTestChains() + + polkadotChain := chainA.(*polkadot.PolkadotChain) + cosmosChain := chainB.(*cosmos.CosmosChain) + + // we explicitly skip path creation as the contract needs to be uploaded before we can create clients. + r := s.ConfigureRelayer(ctx, polkadotChain, cosmosChain, nil, func(options *interchaintest.InterchainBuildOptions) { + options.SkipPathCreation = true + }) + + s.InitGRPCClients(cosmosChain) + + cosmosWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + + file, err := os.Open("contracts/ics10_grandpa_cw_expiry.wasm.gz") + s.Require().NoError(err) + + codeHash := s.PushNewWasmClientProposal(ctx, cosmosChain, cosmosWallet, file) + s.Require().NotEmpty(codeHash, "codehash was empty but should not have been") + + eRep := s.GetRelayerExecReporter() + + // Set client contract hash in cosmos chain config + err = r.SetClientContractHash(ctx, eRep, cosmosChain.Config(), codeHash) + s.Require().NoError(err) + + // Ensure parachain has started (starts 1 session/epoch after relay chain) + err = testutil.WaitForBlocks(ctx, 1, polkadotChain) + s.Require().NoError(err, "polkadot chain failed to make blocks") + + // Fund users on both cosmos and parachain, mints Asset 1 for Alice + fundAmount := int64(12_333_000_000_000) + _, cosmosUser := s.fundUsers(ctx, fundAmount, polkadotChain, cosmosChain) + + pathName := s.GetPathName(0) + err = r.GeneratePath(ctx, eRep, cosmosChain.Config().ChainID, polkadotChain.Config().ChainID, pathName) + s.Require().NoError(err) + + t.Run("add 08-wasm to list of allowed clients", func(t *testing.T) { + allowedClients := s.queryAllowedClientsParam(ctx, cosmosChain) + s.allowWasmClients(ctx, cosmosChain, cosmosWallet, allowedClients) + }) + + // create client pair with subject (bad trusting period) + subjectClientID := clienttypes.FormatClientIdentifier(wasmtypes.Wasm, 0) + // TODO: The hyperspace relayer makes no use of create client opts + // https://github.com/strangelove-ventures/interchaintest/blob/main/relayer/hyperspace/hyperspace_commander.go#L83 + s.SetupClients(ctx, r, ibc.CreateClientOptions{ + TrustingPeriod: modifiedTrustingPeriod.String(), // NOTE: this is hardcoded within the cw contract: ics10_grapnda_cw_expiry.wasm + }) + + // wait for block + err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) + s.Require().NoError(err) + + // wait the bad trusting period + time.Sleep(modifiedTrustingPeriod) + + // create client pair with substitute + substituteClientID := clienttypes.FormatClientIdentifier(wasmtypes.Wasm, 1) + s.SetupClients(ctx, r, ibc.DefaultClientOpts()) + + // wait for block + err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) + s.Require().NoError(err) + + // ensure subject client is expired + status, err := s.clientStatus(ctx, cosmosChain, subjectClientID) + s.Require().NoError(err) + s.Require().Equal(ibcexported.Expired.String(), status, "unexpected subject client status") + + // ensure substitute client is active + status, err = s.clientStatus(ctx, cosmosChain, substituteClientID) + s.Require().NoError(err) + s.Require().Equal(ibcexported.Active.String(), status, "unexpected substitute client status") + + // create and execute a client recovery proposal + authority, err := s.QueryModuleAccountAddress(ctx, govtypes.ModuleName, cosmosChain) + s.Require().NoError(err) + msgRecoverClient := clienttypes.NewMsgRecoverClient(authority.String(), subjectClientID, substituteClientID) + s.Require().NotNil(msgRecoverClient) + s.ExecuteAndPassGovV1Proposal(ctx, msgRecoverClient, cosmosChain, cosmosUser) + + // ensure subject client is active + status, err = s.clientStatus(ctx, cosmosChain, subjectClientID) + s.Require().NoError(err) + s.Require().Equal(ibcexported.Active.String(), status) + + // ensure substitute client is active + status, err = s.clientStatus(ctx, cosmosChain, substituteClientID) + s.Require().NoError(err) + s.Require().Equal(ibcexported.Active.String(), status) +} + +// extractChecksumFromGzippedContent takes a gzipped wasm contract and returns the checksum. +func (s *GrandpaTestSuite) extractChecksumFromGzippedContent(zippedContent []byte) string { + content, err := wasmtypes.Uncompress(zippedContent, wasmtypes.MaxWasmByteSize()) + s.Require().NoError(err) + + checksum32 := sha256.Sum256(content) + return hex.EncodeToString(checksum32[:]) +} + +// PushNewWasmClientProposal submits a new wasm client governance proposal to the chain. +func (s *GrandpaTestSuite) PushNewWasmClientProposal(ctx context.Context, chain *cosmos.CosmosChain, wallet ibc.Wallet, proposalContentReader io.Reader) string { + zippedContent, err := io.ReadAll(proposalContentReader) + s.Require().NoError(err) + + computedChecksum := s.extractChecksumFromGzippedContent(zippedContent) + + s.Require().NoError(err) + message := wasmtypes.MsgStoreCode{ + Signer: authtypes.NewModuleAddress(govtypes.ModuleName).String(), + WasmByteCode: zippedContent, + } + + s.ExecuteAndPassGovV1Proposal(ctx, &message, chain, wallet) + + checksumBz, err := s.QueryWasmCode(ctx, chain, computedChecksum) + s.Require().NoError(err) + + checksum32 := sha256.Sum256(checksumBz) + actualChecksum := hex.EncodeToString(checksum32[:]) + s.Require().Equal(computedChecksum, actualChecksum, "checksum returned from query did not match the computed checksum") + + return actualChecksum +} + +func (s *GrandpaTestSuite) clientStatus(ctx context.Context, chain ibc.Chain, clientID string) (string, error) { + queryClient := s.GetChainGRCPClients(chain).ClientQueryClient + res, err := queryClient.ClientStatus(ctx, &clienttypes.QueryClientStatusRequest{ + ClientId: clientID, + }) + if err != nil { + return "", err + } + + return res.Status, nil +} + +func (s *GrandpaTestSuite) fundUsers(ctx context.Context, fundAmount int64, polkadotChain ibc.Chain, cosmosChain ibc.Chain) (ibc.Wallet, ibc.Wallet) { + users := interchaintest.GetAndFundTestUsers(s.T(), ctx, "user", fundAmount, polkadotChain, cosmosChain) + polkadotUser, cosmosUser := users[0], users[1] + err := testutil.WaitForBlocks(ctx, 2, polkadotChain, cosmosChain) // Only waiting 1 block is flaky for parachain + s.Require().NoError(err, "cosmos or polkadot chain failed to make blocks") + + // Check balances are correct + amount := math.NewInt(fundAmount) + polkadotUserAmount, err := polkadotChain.GetBalance(ctx, polkadotUser.FormattedAddress(), polkadotChain.Config().Denom) + s.Require().NoError(err) + s.Require().True(polkadotUserAmount.Equal(amount), "Initial polkadot user amount not expected") + + parachainUserAmount, err := polkadotChain.GetBalance(ctx, polkadotUser.FormattedAddress(), "") + s.Require().NoError(err) + s.Require().True(parachainUserAmount.Equal(amount), "Initial parachain user amount not expected") + + cosmosUserAmount, err := cosmosChain.GetBalance(ctx, cosmosUser.FormattedAddress(), cosmosChain.Config().Denom) + s.Require().NoError(err) + s.Require().True(cosmosUserAmount.Equal(amount), "Initial cosmos user amount not expected") + + return polkadotUser, cosmosUser +} + +// validateTestConfig ensures that the given test config is valid for this test suite. +func validateTestConfig() { + tc := testsuite.LoadConfig() + if tc.ActiveRelayer != "hyperspace" { + panic(fmt.Errorf("hyperspace relayer must be specified")) + } +} + +// getConfigOverrides returns configuration overrides that will be applied to the simapp. +func getConfigOverrides() map[string]any { + consensusOverrides := make(testutil.Toml) + blockTime := 5 + blockT := (time.Duration(blockTime) * time.Second).String() + consensusOverrides["timeout_commit"] = blockT + consensusOverrides["timeout_propose"] = blockT + + configTomlOverrides := make(testutil.Toml) + configTomlOverrides["consensus"] = consensusOverrides + configTomlOverrides["log_level"] = "info" + + configFileOverrides := make(map[string]any) + configFileOverrides["config/config.toml"] = configTomlOverrides + return configFileOverrides +} + +// GetGrandpaTestChains returns the configured chains for the grandpa test suite. +func (s *GrandpaTestSuite) GetGrandpaTestChains() (ibc.Chain, ibc.Chain) { + return s.GetChains(func(options *testsuite.ChainOptions) { + // configure chain A (polkadot) + options.ChainASpec.ChainName = composable + options.ChainASpec.Type = "polkadot" + options.ChainASpec.ChainID = "rococo-local" + options.ChainASpec.Name = "composable" + options.ChainASpec.Images = []ibc.DockerImage{ + // TODO: https://github.com/cosmos/ibc-go/issues/4965 + { + Repository: "ghcr.io/misko9/polkadot-node", + Version: "local", + UidGid: "1000:1000", + }, + { + Repository: "ghcr.io/misko9/parachain-node", + Version: "latest", + UidGid: "1000:1000", + }, + } + options.ChainASpec.Bin = "polkadot" + options.ChainASpec.Bech32Prefix = composable + options.ChainASpec.Denom = "uDOT" + options.ChainASpec.GasPrices = "" + options.ChainASpec.GasAdjustment = 0 + options.ChainASpec.TrustingPeriod = "" + options.ChainASpec.CoinType = "354" + + // these values are set by default for our cosmos chains, we need to explicitly remove them here. + options.ChainASpec.ModifyGenesis = nil + options.ChainASpec.ConfigFileOverrides = nil + options.ChainASpec.EncodingConfig = nil + + // configure chain B (cosmos) + options.ChainBSpec.ChainName = simd // Set chain name so that a suffix with a "dash" is not appended (required for hyperspace) + options.ChainBSpec.Type = "cosmos" + options.ChainBSpec.Name = "simd" + options.ChainBSpec.ChainID = simd + options.ChainBSpec.Bin = simd + options.ChainBSpec.Bech32Prefix = "cosmos" + + // TODO: hyperspace relayer assumes a denom of "stake", hard code this here right now. + // https://github.com/cosmos/ibc-go/issues/4964 + options.ChainBSpec.Denom = "stake" + options.ChainBSpec.GasPrices = "0.00stake" + options.ChainBSpec.GasAdjustment = 1 + options.ChainBSpec.TrustingPeriod = "504h" + options.ChainBSpec.CoinType = "118" + + options.ChainBSpec.ChainConfig.NoHostMount = false + options.ChainBSpec.ConfigFileOverrides = getConfigOverrides() + options.ChainBSpec.EncodingConfig = testsuite.SDKEncodingConfig() + }) +} + +// queryAllowedClientsParam queries the on-chain allowed clients param for 02-client +func (s *GrandpaTestSuite) queryAllowedClientsParam(ctx context.Context, chain ibc.Chain) []string { + if testvalues.SelfParamsFeatureReleases.IsSupported(chain.Config().Images[0].Version) { + queryClient := s.GetChainGRCPClients(chain).ClientQueryClient + res, err := queryClient.ClientParams(ctx, &clienttypes.QueryClientParamsRequest{}) + s.Require().NoError(err) + + return res.Params.AllowedClients + } + queryClient := s.GetChainGRCPClients(chain).ParamsQueryClient + res, err := queryClient.Params(ctx, ¶msproposaltypes.QueryParamsRequest{ + Subspace: ibcexported.ModuleName, + Key: string(clienttypes.KeyAllowedClients), + }) + s.Require().NoError(err) + + var allowedClients []string + err = cmtjson.Unmarshal([]byte(res.Param.Value), &allowedClients) + s.Require().NoError(err) + + return allowedClients +} + +// allowWasmClients adds 08-wasm to the on-chain allowed clients param for 02-client +func (s *GrandpaTestSuite) allowWasmClients(ctx context.Context, chain ibc.Chain, wallet ibc.Wallet, allowedClients []string) { + govModuleAddress, err := s.QueryModuleAccountAddress(ctx, govtypes.ModuleName, chain) + s.Require().NoError(err) + s.Require().NotNil(govModuleAddress) + + allowedClients = append(allowedClients, wasmtypes.Wasm) + if testvalues.SelfParamsFeatureReleases.IsSupported(chain.Config().Images[0].Version) { + msg := clienttypes.NewMsgUpdateParams(govModuleAddress.String(), clienttypes.NewParams(allowedClients...)) + s.ExecuteAndPassGovV1Proposal(ctx, msg, chain, wallet) + } else { + value, err := cmtjson.Marshal(allowedClients) + s.Require().NoError(err) + changes := []paramsproposaltypes.ParamChange{ + paramsproposaltypes.NewParamChange(ibcexported.ModuleName, string(clienttypes.KeyAllowedClients), string(value)), + } + + proposal := paramsproposaltypes.NewParameterChangeProposal(ibctesting.Title, ibctesting.Description, changes) + s.ExecuteAndPassGovV1Beta1Proposal(ctx, chain, wallet, proposal) + } +} diff --git a/modules/core/02-client/types/params.go b/modules/core/02-client/types/params.go index 81127e5b300..1d632fe2ee9 100644 --- a/modules/core/02-client/types/params.go +++ b/modules/core/02-client/types/params.go @@ -9,6 +9,7 @@ import ( "github.com/cosmos/ibc-go/v7/modules/core/exported" ) +<<<<<<< HEAD var ( // DefaultAllowedClients are the default clients for the AllowedClients parameter. DefaultAllowedClients = []string{exported.Solomachine, exported.Tendermint, exported.Wasm, exported.Localhost} @@ -21,6 +22,10 @@ var ( func ParamKeyTable() paramtypes.KeyTable { return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) } +======= +// DefaultAllowedClients are the default clients for the AllowedClients parameter. +var DefaultAllowedClients = []string{exported.Solomachine, exported.Tendermint, exported.Localhost} +>>>>>>> e3ab9bec (fix: remove 08-wasm from 02-client exported (#5306)) // NewParams creates a new parameter configuration for the ibc client module func NewParams(allowedClients ...string) Params { diff --git a/modules/core/exported/client.go b/modules/core/exported/client.go index 2008fa50b6e..731d7e5edf7 100644 --- a/modules/core/exported/client.go +++ b/modules/core/exported/client.go @@ -19,9 +19,6 @@ const ( // Tendermint is used to indicate that the client uses the Tendermint Consensus Algorithm. Tendermint string = "07-tendermint" - // Wasm is used to indicate that the light client is a on-chain wasm program - Wasm string = "08-wasm" - // Localhost is the client type for the localhost client. Localhost string = "09-localhost" diff --git a/modules/light-clients/08-wasm/keeper/keeper_test.go b/modules/light-clients/08-wasm/keeper/keeper_test.go index 2ddd994de69..8f5d5306765 100644 --- a/modules/light-clients/08-wasm/keeper/keeper_test.go +++ b/modules/light-clients/08-wasm/keeper/keeper_test.go @@ -78,6 +78,8 @@ func (suite *KeeperTestSuite) SetupWasmWithMockVM() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 1) suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + + wasmtesting.AllowWasmClients(suite.chainA) } func (suite *KeeperTestSuite) setupWasmWithMockVM() (ibctesting.TestingApp, map[string]json.RawMessage) { diff --git a/modules/light-clients/08-wasm/testing/wasm_endpoint.go b/modules/light-clients/08-wasm/testing/wasm_endpoint.go index d947939771b..a20a27070fe 100644 --- a/modules/light-clients/08-wasm/testing/wasm_endpoint.go +++ b/modules/light-clients/08-wasm/testing/wasm_endpoint.go @@ -50,3 +50,12 @@ func (endpoint *WasmEndpoint) CreateClient() error { return nil } + +// AllowWasmClients adds 08-wasm to the list of allowed clients +func AllowWasmClients(chain *ibctesting.TestChain) { + ctx := chain.GetContext() + clientKeeper := chain.App.GetIBCKeeper().ClientKeeper + params := clientKeeper.GetParams(ctx) + params.AllowedClients = append(params.AllowedClients, types.Wasm) + clientKeeper.SetParams(ctx, params) +} diff --git a/modules/light-clients/08-wasm/types/client_message.go b/modules/light-clients/08-wasm/types/client_message.go index cf7a0b8a12e..06d0f354756 100644 --- a/modules/light-clients/08-wasm/types/client_message.go +++ b/modules/light-clients/08-wasm/types/client_message.go @@ -10,7 +10,7 @@ var _ exported.ClientMessage = &ClientMessage{} // ClientType is a Wasm light client. func (ClientMessage) ClientType() string { - return exported.Wasm + return Wasm } // ValidateBasic defines a basic validation for the wasm client message. diff --git a/modules/light-clients/08-wasm/types/client_message_test.go b/modules/light-clients/08-wasm/types/client_message_test.go index e0d7dd92556..d81fdeffd95 100644 --- a/modules/light-clients/08-wasm/types/client_message_test.go +++ b/modules/light-clients/08-wasm/types/client_message_test.go @@ -2,7 +2,10 @@ package types_test import ( "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" +<<<<<<< HEAD "github.com/cosmos/ibc-go/v7/modules/core/exported" +======= +>>>>>>> e3ab9bec (fix: remove 08-wasm from 02-client exported (#5306)) ) func (suite *TypesTestSuite) TestClientMessageValidateBasic() { @@ -38,7 +41,7 @@ func (suite *TypesTestSuite) TestClientMessageValidateBasic() { suite.Run(tc.name, func() { clientMessage := tc.clientMessage - suite.Require().Equal(exported.Wasm, clientMessage.ClientType()) + suite.Require().Equal(types.Wasm, clientMessage.ClientType()) err := clientMessage.ValidateBasic() if tc.expPass { diff --git a/modules/light-clients/08-wasm/types/client_state.go b/modules/light-clients/08-wasm/types/client_state.go index 1f52afcf701..b988dda5853 100644 --- a/modules/light-clients/08-wasm/types/client_state.go +++ b/modules/light-clients/08-wasm/types/client_state.go @@ -27,7 +27,7 @@ func NewClientState(data []byte, checksum []byte, height clienttypes.Height) *Cl // ClientType is Wasm light client. func (ClientState) ClientType() string { - return exported.Wasm + return Wasm } // GetLatestHeight returns latest block height. diff --git a/modules/light-clients/08-wasm/types/consensus_state.go b/modules/light-clients/08-wasm/types/consensus_state.go index 08d34c850de..30a81e89efe 100644 --- a/modules/light-clients/08-wasm/types/consensus_state.go +++ b/modules/light-clients/08-wasm/types/consensus_state.go @@ -17,7 +17,7 @@ func NewConsensusState(data []byte) *ConsensusState { // ClientType returns Wasm type. func (ConsensusState) ClientType() string { - return exported.Wasm + return Wasm } // GetTimestamp returns block time in nanoseconds of the header that created consensus state. diff --git a/modules/light-clients/08-wasm/types/consensus_state_test.go b/modules/light-clients/08-wasm/types/consensus_state_test.go index 088ce765329..5c78b337cca 100644 --- a/modules/light-clients/08-wasm/types/consensus_state_test.go +++ b/modules/light-clients/08-wasm/types/consensus_state_test.go @@ -2,7 +2,10 @@ package types_test import ( "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" +<<<<<<< HEAD "github.com/cosmos/ibc-go/v7/modules/core/exported" +======= +>>>>>>> e3ab9bec (fix: remove 08-wasm from 02-client exported (#5306)) ) func (suite *TypesTestSuite) TestConsensusStateValidateBasic() { @@ -31,7 +34,7 @@ func (suite *TypesTestSuite) TestConsensusStateValidateBasic() { for _, tc := range testCases { suite.Run(tc.name, func() { // check just to increase coverage - suite.Require().Equal(exported.Wasm, tc.consensusState.ClientType()) + suite.Require().Equal(types.Wasm, tc.consensusState.ClientType()) err := tc.consensusState.ValidateBasic() if tc.expectPass { diff --git a/modules/light-clients/08-wasm/types/keys.go b/modules/light-clients/08-wasm/types/keys.go index 44e1bfd5420..9e0a6456b6d 100644 --- a/modules/light-clients/08-wasm/types/keys.go +++ b/modules/light-clients/08-wasm/types/keys.go @@ -7,6 +7,11 @@ const ( // StoreKey is the store key string for 08-wasm StoreKey = ModuleName +<<<<<<< HEAD // KeyChecksums is the key under which all checksums are stored KeyChecksums = "checksums" +======= + // Wasm is the client type for IBC light clients created using 08-wasm + Wasm = ModuleName +>>>>>>> e3ab9bec (fix: remove 08-wasm from 02-client exported (#5306)) ) diff --git a/modules/light-clients/08-wasm/types/types_test.go b/modules/light-clients/08-wasm/types/types_test.go index 182a72fa308..051b5815a95 100644 --- a/modules/light-clients/08-wasm/types/types_test.go +++ b/modules/light-clients/08-wasm/types/types_test.go @@ -79,6 +79,8 @@ func (suite *TypesTestSuite) SetupWasmWithMockVM() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 1) suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) suite.checksum = storeWasmCode(suite, wasmtesting.Code) + + wasmtesting.AllowWasmClients(suite.chainA) } func (suite *TypesTestSuite) setupWasmWithMockVM() (ibctesting.TestingApp, map[string]json.RawMessage) { diff --git a/modules/light-clients/08-wasm/types/validation.go b/modules/light-clients/08-wasm/types/validation.go index 3b42341f14c..6e14b626dcd 100644 --- a/modules/light-clients/08-wasm/types/validation.go +++ b/modules/light-clients/08-wasm/types/validation.go @@ -5,9 +5,14 @@ import ( errorsmod "cosmossdk.io/errors" +<<<<<<< HEAD clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" host "github.com/cosmos/ibc-go/v7/modules/core/24-host" "github.com/cosmos/ibc-go/v7/modules/core/exported" +======= + clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" +>>>>>>> e3ab9bec (fix: remove 08-wasm from 02-client exported (#5306)) ) const maxWasmSize = 3 * 1024 * 1024 @@ -50,8 +55,8 @@ func ValidateClientID(clientID string) error { return errorsmod.Wrapf(host.ErrInvalidID, "invalid client identifier %s", clientID) } - if !strings.HasPrefix(clientID, exported.Wasm) { - return errorsmod.Wrapf(host.ErrInvalidID, "client identifier %s does not contain %s prefix", clientID, exported.Wasm) + if !strings.HasPrefix(clientID, Wasm) { + return errorsmod.Wrapf(host.ErrInvalidID, "client identifier %s does not contain %s prefix", clientID, Wasm) } return nil diff --git a/modules/light-clients/08-wasm/types/validation_test.go b/modules/light-clients/08-wasm/types/validation_test.go index f16fdd32231..4039bd1f2a5 100644 --- a/modules/light-clients/08-wasm/types/validation_test.go +++ b/modules/light-clients/08-wasm/types/validation_test.go @@ -9,11 +9,16 @@ import ( errorsmod "cosmossdk.io/errors" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" +<<<<<<< HEAD host "github.com/cosmos/ibc-go/v7/modules/core/24-host" "github.com/cosmos/ibc-go/v7/modules/core/exported" ibctesting "github.com/cosmos/ibc-go/v7/testing" wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" +======= + host "github.com/cosmos/ibc-go/v8/modules/core/24-host" + ibctesting "github.com/cosmos/ibc-go/v8/testing" +>>>>>>> e3ab9bec (fix: remove 08-wasm from 02-client exported (#5306)) ) func TestValidateWasmCode(t *testing.T) { @@ -144,7 +149,7 @@ func TestValidateClientID(t *testing.T) { func() { clientID = ibctesting.FirstClientID }, - errorsmod.Wrapf(host.ErrInvalidID, "client identifier %s does not contain %s prefix", ibctesting.FirstClientID, exported.Wasm), + errorsmod.Wrapf(host.ErrInvalidID, "client identifier %s does not contain %s prefix", ibctesting.FirstClientID, types.Wasm), }, } From d9cbb67b8858a36badbf95e0f57cf03ef19a1103 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 5 Dec 2023 13:04:49 +0100 Subject: [PATCH 2/3] fix conflicts --- modules/core/02-client/types/params.go | 7 +------ .../light-clients/08-wasm/types/client_message_test.go | 4 ---- .../light-clients/08-wasm/types/consensus_state_test.go | 4 ---- modules/light-clients/08-wasm/types/keys.go | 8 +++----- modules/light-clients/08-wasm/types/validation.go | 6 ------ modules/light-clients/08-wasm/types/validation_test.go | 6 ------ 6 files changed, 4 insertions(+), 31 deletions(-) diff --git a/modules/core/02-client/types/params.go b/modules/core/02-client/types/params.go index 1d632fe2ee9..b0c43b156ef 100644 --- a/modules/core/02-client/types/params.go +++ b/modules/core/02-client/types/params.go @@ -9,10 +9,9 @@ import ( "github.com/cosmos/ibc-go/v7/modules/core/exported" ) -<<<<<<< HEAD var ( // DefaultAllowedClients are the default clients for the AllowedClients parameter. - DefaultAllowedClients = []string{exported.Solomachine, exported.Tendermint, exported.Wasm, exported.Localhost} + DefaultAllowedClients = []string{exported.Solomachine, exported.Tendermint, exported.Localhost} // KeyAllowedClients is store's key for AllowedClients Params KeyAllowedClients = []byte("AllowedClients") @@ -22,10 +21,6 @@ var ( func ParamKeyTable() paramtypes.KeyTable { return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) } -======= -// DefaultAllowedClients are the default clients for the AllowedClients parameter. -var DefaultAllowedClients = []string{exported.Solomachine, exported.Tendermint, exported.Localhost} ->>>>>>> e3ab9bec (fix: remove 08-wasm from 02-client exported (#5306)) // NewParams creates a new parameter configuration for the ibc client module func NewParams(allowedClients ...string) Params { diff --git a/modules/light-clients/08-wasm/types/client_message_test.go b/modules/light-clients/08-wasm/types/client_message_test.go index d81fdeffd95..c405e3fe0f8 100644 --- a/modules/light-clients/08-wasm/types/client_message_test.go +++ b/modules/light-clients/08-wasm/types/client_message_test.go @@ -2,10 +2,6 @@ package types_test import ( "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" -<<<<<<< HEAD - "github.com/cosmos/ibc-go/v7/modules/core/exported" -======= ->>>>>>> e3ab9bec (fix: remove 08-wasm from 02-client exported (#5306)) ) func (suite *TypesTestSuite) TestClientMessageValidateBasic() { diff --git a/modules/light-clients/08-wasm/types/consensus_state_test.go b/modules/light-clients/08-wasm/types/consensus_state_test.go index 5c78b337cca..76a75be6525 100644 --- a/modules/light-clients/08-wasm/types/consensus_state_test.go +++ b/modules/light-clients/08-wasm/types/consensus_state_test.go @@ -2,10 +2,6 @@ package types_test import ( "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" -<<<<<<< HEAD - "github.com/cosmos/ibc-go/v7/modules/core/exported" -======= ->>>>>>> e3ab9bec (fix: remove 08-wasm from 02-client exported (#5306)) ) func (suite *TypesTestSuite) TestConsensusStateValidateBasic() { diff --git a/modules/light-clients/08-wasm/types/keys.go b/modules/light-clients/08-wasm/types/keys.go index 9e0a6456b6d..7d64d50f2bb 100644 --- a/modules/light-clients/08-wasm/types/keys.go +++ b/modules/light-clients/08-wasm/types/keys.go @@ -7,11 +7,9 @@ const ( // StoreKey is the store key string for 08-wasm StoreKey = ModuleName -<<<<<<< HEAD - // KeyChecksums is the key under which all checksums are stored - KeyChecksums = "checksums" -======= // Wasm is the client type for IBC light clients created using 08-wasm Wasm = ModuleName ->>>>>>> e3ab9bec (fix: remove 08-wasm from 02-client exported (#5306)) + + // KeyChecksums is the key under which all checksums are stored + KeyChecksums = "checksums" ) diff --git a/modules/light-clients/08-wasm/types/validation.go b/modules/light-clients/08-wasm/types/validation.go index 6e14b626dcd..f76afd6897f 100644 --- a/modules/light-clients/08-wasm/types/validation.go +++ b/modules/light-clients/08-wasm/types/validation.go @@ -5,14 +5,8 @@ import ( errorsmod "cosmossdk.io/errors" -<<<<<<< HEAD clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" host "github.com/cosmos/ibc-go/v7/modules/core/24-host" - "github.com/cosmos/ibc-go/v7/modules/core/exported" -======= - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - host "github.com/cosmos/ibc-go/v8/modules/core/24-host" ->>>>>>> e3ab9bec (fix: remove 08-wasm from 02-client exported (#5306)) ) const maxWasmSize = 3 * 1024 * 1024 diff --git a/modules/light-clients/08-wasm/types/validation_test.go b/modules/light-clients/08-wasm/types/validation_test.go index 4039bd1f2a5..1c568d29340 100644 --- a/modules/light-clients/08-wasm/types/validation_test.go +++ b/modules/light-clients/08-wasm/types/validation_test.go @@ -9,16 +9,10 @@ import ( errorsmod "cosmossdk.io/errors" "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" -<<<<<<< HEAD host "github.com/cosmos/ibc-go/v7/modules/core/24-host" - "github.com/cosmos/ibc-go/v7/modules/core/exported" ibctesting "github.com/cosmos/ibc-go/v7/testing" wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" -======= - host "github.com/cosmos/ibc-go/v8/modules/core/24-host" - ibctesting "github.com/cosmos/ibc-go/v8/testing" ->>>>>>> e3ab9bec (fix: remove 08-wasm from 02-client exported (#5306)) ) func TestValidateWasmCode(t *testing.T) { From aa490e5a00f755e5d8a33b5403e2871c2b7f9c25 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 5 Dec 2023 13:06:00 +0100 Subject: [PATCH 3/3] delete e2e --- e2e/tests/wasm/grandpa_test.go | 832 --------------------------------- 1 file changed, 832 deletions(-) delete mode 100644 e2e/tests/wasm/grandpa_test.go diff --git a/e2e/tests/wasm/grandpa_test.go b/e2e/tests/wasm/grandpa_test.go deleted file mode 100644 index 6aa250b1885..00000000000 --- a/e2e/tests/wasm/grandpa_test.go +++ /dev/null @@ -1,832 +0,0 @@ -//go:build !test_e2e - -package wasm - -import ( - "context" - "crypto/sha256" - "encoding/hex" - "fmt" - "io" - "os" - "testing" - "time" - - "github.com/strangelove-ventures/interchaintest/v8" - "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" - "github.com/strangelove-ventures/interchaintest/v8/chain/polkadot" - "github.com/strangelove-ventures/interchaintest/v8/ibc" - "github.com/strangelove-ventures/interchaintest/v8/testutil" - testifysuite "github.com/stretchr/testify/suite" - - cmtjson "github.com/cometbft/cometbft/libs/json" - - "cosmossdk.io/math" - - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - paramsproposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" - - "github.com/cosmos/ibc-go/e2e/testsuite" - "github.com/cosmos/ibc-go/e2e/testvalues" - wasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" - transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" - ibctesting "github.com/cosmos/ibc-go/v8/testing" -) - -const ( - composable = "composable" - simd = "simd" - wasmSimdImage = "ghcr.io/cosmos/ibc-go-wasm-simd" - - defaultWasmClientID = "08-wasm-0" -) - -func TestGrandpaTestSuite(t *testing.T) { - // this test suite only works with the hyperspace relayer, for now hard code this here. - // this will enforce that the hyperspace relayer is used in CI. - t.Setenv(testsuite.RelayerIDEnv, "hyperspace") - - // TODO: this value should be passed in via the config file / CI, not hard coded in the test. - // This configuration can be handled in https://github.com/cosmos/ibc-go/issues/4697 - if testsuite.IsCI() && !testsuite.IsFork() { - t.Setenv(testsuite.ChainImageEnv, wasmSimdImage) - } - - // wasm tests require a longer voting period to account for the time it takes to upload a contract. - testvalues.VotingPeriod = time.Minute * 5 - - validateTestConfig() - testifysuite.Run(t, new(GrandpaTestSuite)) -} - -type GrandpaTestSuite struct { - testsuite.E2ETestSuite -} - -// TestMsgTransfer_Succeeds_GrandpaContract features -// * sets up a Polkadot parachain -// * sets up a Cosmos chain -// * sets up the Hyperspace relayer -// * Funds a user wallet on both chains -// * Pushes a wasm client contract to the Cosmos chain -// * create client, connection, and channel in relayer -// * start relayer -// * send transfer over ibc -func (s *GrandpaTestSuite) TestMsgTransfer_Succeeds_GrandpaContract() { - ctx := context.Background() - t := s.T() - - chainA, chainB := s.GetGrandpaTestChains() - - polkadotChain := chainA.(*polkadot.PolkadotChain) - cosmosChain := chainB.(*cosmos.CosmosChain) - - // we explicitly skip path creation as the contract needs to be uploaded before we can create clients. - r := s.ConfigureRelayer(ctx, polkadotChain, cosmosChain, nil, func(options *interchaintest.InterchainBuildOptions) { - options.SkipPathCreation = true - }) - - s.InitGRPCClients(cosmosChain) - - cosmosWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) - - file, err := os.Open("contracts/ics10_grandpa_cw.wasm.gz") - s.Require().NoError(err) - - checksum := s.PushNewWasmClientProposal(ctx, cosmosChain, cosmosWallet, file) - - s.Require().NotEmpty(checksum, "checksum was empty but should not have been") - - eRep := s.GetRelayerExecReporter() - - // Set client contract hash in cosmos chain config - err = r.SetClientContractHash(ctx, eRep, cosmosChain.Config(), checksum) - s.Require().NoError(err) - - // Ensure parachain has started (starts 1 session/epoch after relay chain) - err = testutil.WaitForBlocks(ctx, 1, polkadotChain) - s.Require().NoError(err, "polkadot chain failed to make blocks") - - // Fund users on both cosmos and parachain, mints Asset 1 for Alice - fundAmount := int64(12_333_000_000_000) - polkadotUser, cosmosUser := s.fundUsers(ctx, fundAmount, polkadotChain, cosmosChain) - - // TODO: this can be refactored to broadcast a MsgTransfer instead of CLI. - // https://github.com/cosmos/ibc-go/issues/4963 - amountToSend := int64(1_770_000) - transfer := ibc.WalletAmount{ - Address: polkadotUser.FormattedAddress(), - Denom: cosmosChain.Config().Denom, - Amount: math.NewInt(amountToSend), - } - - pathName := s.GetPathName(0) - - err = r.GeneratePath(ctx, eRep, cosmosChain.Config().ChainID, polkadotChain.Config().ChainID, pathName) - s.Require().NoError(err) - - t.Run("add 08-wasm to list of allowed clients", func(t *testing.T) { - allowedClients := s.queryAllowedClientsParam(ctx, cosmosChain) - s.allowWasmClients(ctx, cosmosChain, cosmosWallet, allowedClients) - }) - - // Create new clients - err = r.CreateClients(ctx, eRep, pathName, ibc.DefaultClientOpts()) - s.Require().NoError(err) - err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) // these 1 block waits seem to be needed to reduce flakiness - s.Require().NoError(err) - - // Create a new connection - err = r.CreateConnections(ctx, eRep, pathName) - s.Require().NoError(err) - err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) - s.Require().NoError(err) - - // Create a new channel & get channels from each chain - err = r.CreateChannel(ctx, eRep, pathName, ibc.DefaultChannelOpts()) - s.Require().NoError(err) - err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) - s.Require().NoError(err) - - // Start relayer - s.Require().NoError(r.StartRelayer(ctx, eRep, pathName)) - - t.Run("send successful IBC transfer from Cosmos to Polkadot parachain", func(t *testing.T) { - // Send 1.77 stake from cosmosUser to parachainUser - tx, err := cosmosChain.SendIBCTransfer(ctx, "channel-0", cosmosUser.KeyName(), transfer, ibc.TransferOptions{}) - s.Require().NoError(tx.Validate(), "source ibc transfer tx is invalid") - s.Require().NoError(err) - // verify token balance for cosmos user has decreased - balance, err := cosmosChain.GetBalance(ctx, cosmosUser.FormattedAddress(), cosmosChain.Config().Denom) - s.Require().NoError(err) - s.Require().Equal(balance, math.NewInt(fundAmount-amountToSend), "unexpected cosmos user balance after first tx") - err = testutil.WaitForBlocks(ctx, 15, cosmosChain, polkadotChain) - s.Require().NoError(err) - - // Verify tokens arrived on parachain user - parachainUserStake, err := polkadotChain.GetIbcBalance(ctx, string(polkadotUser.Address()), 2) - s.Require().NoError(err) - s.Require().Equal(amountToSend, parachainUserStake.Amount.Int64(), "unexpected parachain user balance after first tx") - }) - - t.Run("send two successful IBC transfers from Polkadot parachain to Cosmos, first with ibc denom, second with parachain denom", func(t *testing.T) { - // Send 1.16 stake from parachainUser to cosmosUser - amountToReflect := int64(1_160_000) - reflectTransfer := ibc.WalletAmount{ - Address: cosmosUser.FormattedAddress(), - Denom: "2", // stake - Amount: math.NewInt(amountToReflect), - } - _, err := polkadotChain.SendIBCTransfer(ctx, "channel-0", polkadotUser.KeyName(), reflectTransfer, ibc.TransferOptions{}) - s.Require().NoError(err) - - // Send 1.88 "UNIT" from Alice to cosmosUser - amountUnits := math.NewInt(1_880_000_000_000) - unitTransfer := ibc.WalletAmount{ - Address: cosmosUser.FormattedAddress(), - Denom: "1", // UNIT - Amount: amountUnits, - } - _, err = polkadotChain.SendIBCTransfer(ctx, "channel-0", "alice", unitTransfer, ibc.TransferOptions{}) - s.Require().NoError(err) - - // Wait for MsgRecvPacket on cosmos chain - finalStakeBal := math.NewInt(fundAmount - amountToSend + amountToReflect) - err = cosmos.PollForBalance(ctx, cosmosChain, 20, ibc.WalletAmount{ - Address: cosmosUser.FormattedAddress(), - Denom: cosmosChain.Config().Denom, - Amount: finalStakeBal, - }) - s.Require().NoError(err) - - // Wait for a new update state - err = testutil.WaitForBlocks(ctx, 5, cosmosChain, polkadotChain) - s.Require().NoError(err) - - // Verify cosmos user's final "stake" balance - cosmosUserStakeBal, err := cosmosChain.GetBalance(ctx, cosmosUser.FormattedAddress(), cosmosChain.Config().Denom) - s.Require().NoError(err) - s.Require().True(cosmosUserStakeBal.Equal(finalStakeBal)) - - // Verify cosmos user's final "unit" balance - unitDenomTrace := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom("transfer", "channel-0", "UNIT")) - cosmosUserUnitBal, err := cosmosChain.GetBalance(ctx, cosmosUser.FormattedAddress(), unitDenomTrace.IBCDenom()) - s.Require().NoError(err) - s.Require().True(cosmosUserUnitBal.Equal(amountUnits)) - - // Verify parachain user's final "unit" balance (will be less than expected due gas costs for stake tx) - parachainUserUnits, err := polkadotChain.GetIbcBalance(ctx, string(polkadotUser.Address()), 1) - s.Require().NoError(err) - s.Require().True(parachainUserUnits.Amount.LTE(math.NewInt(fundAmount)), "parachain user's final unit amount not expected") - - // Verify parachain user's final "stake" balance - parachainUserStake, err := polkadotChain.GetIbcBalance(ctx, string(polkadotUser.Address()), 2) - s.Require().NoError(err) - s.Require().True(parachainUserStake.Amount.Equal(math.NewInt(amountToSend-amountToReflect)), "parachain user's final stake amount not expected") - }) -} - -// TestMsgTransfer_TimesOut_GrandpaContract -// sets up cosmos and polkadot chains, hyperspace relayer, and funds users on both chains -// * sends transfer over ibc channel, this transfer should timeout -func (s *GrandpaTestSuite) TestMsgTransfer_TimesOut_GrandpaContract() { - ctx := context.Background() - t := s.T() - - chainA, chainB := s.GetGrandpaTestChains() - - polkadotChain := chainA.(*polkadot.PolkadotChain) - cosmosChain := chainB.(*cosmos.CosmosChain) - - // we explicitly skip path creation as the contract needs to be uploaded before we can create clients. - r := s.ConfigureRelayer(ctx, polkadotChain, cosmosChain, nil, func(options *interchaintest.InterchainBuildOptions) { - options.SkipPathCreation = true - }) - - s.InitGRPCClients(cosmosChain) - - cosmosWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) - - file, err := os.Open("contracts/ics10_grandpa_cw.wasm.gz") - s.Require().NoError(err) - - checksum := s.PushNewWasmClientProposal(ctx, cosmosChain, cosmosWallet, file) - - s.Require().NotEmpty(checksum, "checksum was empty but should not have been") - - eRep := s.GetRelayerExecReporter() - - // Set client contract hash in cosmos chain config - err = r.SetClientContractHash(ctx, eRep, cosmosChain.Config(), checksum) - s.Require().NoError(err) - - // Ensure parachain has started (starts 1 session/epoch after relay chain) - err = testutil.WaitForBlocks(ctx, 1, polkadotChain) - s.Require().NoError(err, "polkadot chain failed to make blocks") - - // Fund users on both cosmos and parachain, mints Asset 1 for Alice - fundAmount := int64(12_333_000_000_000) - polkadotUser, cosmosUser := s.fundUsers(ctx, fundAmount, polkadotChain, cosmosChain) - - // TODO: this can be refactored to broadcast a MsgTransfer instead of CLI. - // https://github.com/cosmos/ibc-go/issues/4963 - amountToSend := int64(1_770_000) - transfer := ibc.WalletAmount{ - Address: polkadotUser.FormattedAddress(), - Denom: cosmosChain.Config().Denom, - Amount: math.NewInt(amountToSend), - } - - pathName := s.GetPathName(0) - - err = r.GeneratePath(ctx, eRep, cosmosChain.Config().ChainID, polkadotChain.Config().ChainID, pathName) - s.Require().NoError(err) - - t.Run("add 08-wasm to list of allowed clients", func(t *testing.T) { - allowedClients := s.queryAllowedClientsParam(ctx, cosmosChain) - s.allowWasmClients(ctx, cosmosChain, cosmosWallet, allowedClients) - }) - - // Create new clients - err = r.CreateClients(ctx, eRep, pathName, ibc.DefaultClientOpts()) - s.Require().NoError(err) - err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) // these 1 block waits seem to be needed to reduce flakiness - s.Require().NoError(err) - - // Create a new connection - err = r.CreateConnections(ctx, eRep, pathName) - s.Require().NoError(err) - err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) - s.Require().NoError(err) - - // Create a new channel & get channels from each chain - err = r.CreateChannel(ctx, eRep, pathName, ibc.DefaultChannelOpts()) - s.Require().NoError(err) - err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) - s.Require().NoError(err) - - // Start relayer - s.Require().NoError(r.StartRelayer(ctx, eRep, pathName)) - - t.Run("IBC transfer from Cosmos chain to Polkadot parachain times out", func(t *testing.T) { - // Stop relayer - s.Require().NoError(r.StopRelayer(ctx, s.GetRelayerExecReporter())) - - tx, err := cosmosChain.SendIBCTransfer(ctx, "channel-0", cosmosUser.KeyName(), transfer, ibc.TransferOptions{Timeout: testvalues.ImmediatelyTimeout()}) - s.Require().NoError(err) - s.Require().NoError(tx.Validate(), "source ibc transfer tx is invalid") - time.Sleep(time.Nanosecond * 1) // want it to timeout immediately - - // check that tokens are escrowed - actualBalance, err := cosmosChain.GetBalance(ctx, cosmosUser.FormattedAddress(), cosmosChain.Config().Denom) - s.Require().NoError(err) - expected := fundAmount - amountToSend - s.Require().Equal(expected, actualBalance.Int64()) - - // start relayer - s.Require().NoError(r.StartRelayer(ctx, s.GetRelayerExecReporter(), s.GetPathName(0))) - err = testutil.WaitForBlocks(ctx, 15, polkadotChain, cosmosChain) - s.Require().NoError(err) - - // ensure that receiver on parachain did not receive any tokens - receiverBalance, err := polkadotChain.GetIbcBalance(ctx, polkadotUser.FormattedAddress(), 2) - s.Require().NoError(err) - s.Require().Equal(int64(0), receiverBalance.Amount.Int64()) - - // check that tokens have been refunded to sender address - senderBalance, err := cosmosChain.GetBalance(ctx, cosmosUser.FormattedAddress(), cosmosChain.Config().Denom) - s.Require().NoError(err) - s.Require().Equal(fundAmount, senderBalance.Int64()) - }) -} - -// TestMsgMigrateContract_Success_GrandpaContract features -// * sets up a Polkadot parachain -// * sets up a Cosmos chain -// * sets up the Hyperspace relayer -// * Funds a user wallet on both chains -// * Pushes a wasm client contract to the Cosmos chain -// * create client in relayer -// * Pushes a new wasm client contract to the Cosmos chain -// * Migrates the wasm client contract -func (s *GrandpaTestSuite) TestMsgMigrateContract_Success_GrandpaContract() { - ctx := context.Background() - t := s.T() - - chainA, chainB := s.GetGrandpaTestChains() - - polkadotChain := chainA.(*polkadot.PolkadotChain) - cosmosChain := chainB.(*cosmos.CosmosChain) - - // we explicitly skip path creation as the contract needs to be uploaded before we can create clients. - r := s.ConfigureRelayer(ctx, polkadotChain, cosmosChain, nil, func(options *interchaintest.InterchainBuildOptions) { - options.SkipPathCreation = true - }) - - s.InitGRPCClients(cosmosChain) - - cosmosWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) - - file, err := os.Open("contracts/ics10_grandpa_cw.wasm.gz") - s.Require().NoError(err) - - checksum := s.PushNewWasmClientProposal(ctx, cosmosChain, cosmosWallet, file) - - s.Require().NotEmpty(checksum, "checksum was empty but should not have been") - - eRep := s.GetRelayerExecReporter() - - // Set client contract hash in cosmos chain config - err = r.SetClientContractHash(ctx, eRep, cosmosChain.Config(), checksum) - s.Require().NoError(err) - - // Ensure parachain has started (starts 1 session/epoch after relay chain) - err = testutil.WaitForBlocks(ctx, 1, polkadotChain) - s.Require().NoError(err, "polkadot chain failed to make blocks") - - pathName := s.GetPathName(0) - - err = r.GeneratePath(ctx, eRep, cosmosChain.Config().ChainID, polkadotChain.Config().ChainID, pathName) - s.Require().NoError(err) - - t.Run("add 08-wasm to list of allowed clients", func(t *testing.T) { - allowedClients := s.queryAllowedClientsParam(ctx, cosmosChain) - s.allowWasmClients(ctx, cosmosChain, cosmosWallet, allowedClients) - }) - - // Create new clients - err = r.CreateClients(ctx, eRep, pathName, ibc.DefaultClientOpts()) - s.Require().NoError(err) - err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) // these 1 block waits seem to be needed to reduce flakiness - s.Require().NoError(err) - - // Do not start relayer - - // This contract is a dummy contract that will always succeed migration. - // Other entry points are unimplemented. - migrateFile, err := os.Open("contracts/migrate_success.wasm.gz") - s.Require().NoError(err) - - // First Store the code - newChecksum := s.PushNewWasmClientProposal(ctx, cosmosChain, cosmosWallet, migrateFile) - s.Require().NotEmpty(newChecksum, "checksum was empty but should not have been") - - newChecksumBz, err := hex.DecodeString(newChecksum) - s.Require().NoError(err) - - // Attempt to migrate the contract - message := wasmtypes.NewMsgMigrateContract( - authtypes.NewModuleAddress(govtypes.ModuleName).String(), - defaultWasmClientID, - newChecksumBz, - []byte("{}"), - ) - - s.ExecuteAndPassGovV1Proposal(ctx, message, cosmosChain, cosmosWallet) - - clientState, err := s.QueryClientState(ctx, cosmosChain, defaultWasmClientID) - s.Require().NoError(err) - - wasmClientState, ok := clientState.(*wasmtypes.ClientState) - s.Require().True(ok) - - s.Require().Equal(newChecksumBz, wasmClientState.Checksum) -} - -// TestMsgMigrateContract_ContractError_GrandpaContract features -// * sets up a Polkadot parachain -// * sets up a Cosmos chain -// * sets up the Hyperspace relayer -// * Funds a user wallet on both chains -// * Pushes a wasm client contract to the Cosmos chain -// * create client in relayer -// * Pushes a new wasm client contract to the Cosmos chain -// * Migrates the wasm client contract with a contract that will always fail migration -func (s *GrandpaTestSuite) TestMsgMigrateContract_ContractError_GrandpaContract() { - ctx := context.Background() - t := s.T() - - chainA, chainB := s.GetGrandpaTestChains() - - polkadotChain := chainA.(*polkadot.PolkadotChain) - cosmosChain := chainB.(*cosmos.CosmosChain) - - // we explicitly skip path creation as the contract needs to be uploaded before we can create clients. - r := s.ConfigureRelayer(ctx, polkadotChain, cosmosChain, nil, func(options *interchaintest.InterchainBuildOptions) { - options.SkipPathCreation = true - }) - - s.InitGRPCClients(cosmosChain) - - cosmosWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) - - file, err := os.Open("contracts/ics10_grandpa_cw.wasm.gz") - s.Require().NoError(err) - checksum := s.PushNewWasmClientProposal(ctx, cosmosChain, cosmosWallet, file) - - s.Require().NotEmpty(checksum, "checksum was empty but should not have been") - - eRep := s.GetRelayerExecReporter() - - // Set client contract hash in cosmos chain config - err = r.SetClientContractHash(ctx, eRep, cosmosChain.Config(), checksum) - s.Require().NoError(err) - - // Ensure parachain has started (starts 1 session/epoch after relay chain) - err = testutil.WaitForBlocks(ctx, 1, polkadotChain) - s.Require().NoError(err, "polkadot chain failed to make blocks") - - pathName := s.GetPathName(0) - - err = r.GeneratePath(ctx, eRep, cosmosChain.Config().ChainID, polkadotChain.Config().ChainID, pathName) - s.Require().NoError(err) - - t.Run("add 08-wasm to list of allowed clients", func(t *testing.T) { - allowedClients := s.queryAllowedClientsParam(ctx, cosmosChain) - s.allowWasmClients(ctx, cosmosChain, cosmosWallet, allowedClients) - }) - - // Create new clients - err = r.CreateClients(ctx, eRep, pathName, ibc.DefaultClientOpts()) - s.Require().NoError(err) - err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) // these 1 block waits seem to be needed to reduce flakiness - s.Require().NoError(err) - - // Do not start the relayer - - // This contract is a dummy contract that will always fail migration. - // Other entry points are unimplemented. - migrateFile, err := os.Open("contracts/migrate_error.wasm.gz") - s.Require().NoError(err) - - // First Store the code - newChecksum := s.PushNewWasmClientProposal(ctx, cosmosChain, cosmosWallet, migrateFile) - s.Require().NotEmpty(newChecksum, "checksum was empty but should not have been") - - newChecksumBz, err := hex.DecodeString(newChecksum) - s.Require().NoError(err) - - // Attempt to migrate the contract - message := wasmtypes.NewMsgMigrateContract( - authtypes.NewModuleAddress(govtypes.ModuleName).String(), - defaultWasmClientID, - newChecksumBz, - []byte("{}"), - ) - - err = s.ExecuteGovV1Proposal(ctx, message, cosmosChain, cosmosWallet) - // This is the error string that is returned from the contract - s.Require().ErrorContains(err, "migration not supported") -} - -// TestRecoverClient_Succeeds_GrandpaContract features: -// * setup cosmos and polkadot substrates nodes -// * funds test user wallets on both chains -// * stores a wasm client contract on the cosmos chain -// * creates a subject client using the hyperspace relayer -// * waits the expiry period and asserts the subject client status has expired -// * creates a substitute client using the hyperspace relayer -// * executes a gov proposal to recover the expired client -// * asserts the status of the subject client has been restored to active -// NOTE: The testcase features a modified grandpa client contract compiled as: -// - ics10_grandpa_cw_expiry.wasm.gz -// This contract modifies the unbonding period to 1600s with the trusting period being calculated as (unbonding period / 3). -func (s *GrandpaTestSuite) TestRecoverClient_Succeeds_GrandpaContract() { - ctx := context.Background() - t := s.T() - - // set the trusting period to a value which will still be valid upon client creation, but invalid before the first update - // the contract uses 1600s as the unbonding period with the trusting period evaluating to (unbonding period / 3) - modifiedTrustingPeriod := (1600 * time.Second) / 3 - - chainA, chainB := s.GetGrandpaTestChains() - - polkadotChain := chainA.(*polkadot.PolkadotChain) - cosmosChain := chainB.(*cosmos.CosmosChain) - - // we explicitly skip path creation as the contract needs to be uploaded before we can create clients. - r := s.ConfigureRelayer(ctx, polkadotChain, cosmosChain, nil, func(options *interchaintest.InterchainBuildOptions) { - options.SkipPathCreation = true - }) - - s.InitGRPCClients(cosmosChain) - - cosmosWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) - - file, err := os.Open("contracts/ics10_grandpa_cw_expiry.wasm.gz") - s.Require().NoError(err) - - codeHash := s.PushNewWasmClientProposal(ctx, cosmosChain, cosmosWallet, file) - s.Require().NotEmpty(codeHash, "codehash was empty but should not have been") - - eRep := s.GetRelayerExecReporter() - - // Set client contract hash in cosmos chain config - err = r.SetClientContractHash(ctx, eRep, cosmosChain.Config(), codeHash) - s.Require().NoError(err) - - // Ensure parachain has started (starts 1 session/epoch after relay chain) - err = testutil.WaitForBlocks(ctx, 1, polkadotChain) - s.Require().NoError(err, "polkadot chain failed to make blocks") - - // Fund users on both cosmos and parachain, mints Asset 1 for Alice - fundAmount := int64(12_333_000_000_000) - _, cosmosUser := s.fundUsers(ctx, fundAmount, polkadotChain, cosmosChain) - - pathName := s.GetPathName(0) - err = r.GeneratePath(ctx, eRep, cosmosChain.Config().ChainID, polkadotChain.Config().ChainID, pathName) - s.Require().NoError(err) - - t.Run("add 08-wasm to list of allowed clients", func(t *testing.T) { - allowedClients := s.queryAllowedClientsParam(ctx, cosmosChain) - s.allowWasmClients(ctx, cosmosChain, cosmosWallet, allowedClients) - }) - - // create client pair with subject (bad trusting period) - subjectClientID := clienttypes.FormatClientIdentifier(wasmtypes.Wasm, 0) - // TODO: The hyperspace relayer makes no use of create client opts - // https://github.com/strangelove-ventures/interchaintest/blob/main/relayer/hyperspace/hyperspace_commander.go#L83 - s.SetupClients(ctx, r, ibc.CreateClientOptions{ - TrustingPeriod: modifiedTrustingPeriod.String(), // NOTE: this is hardcoded within the cw contract: ics10_grapnda_cw_expiry.wasm - }) - - // wait for block - err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) - s.Require().NoError(err) - - // wait the bad trusting period - time.Sleep(modifiedTrustingPeriod) - - // create client pair with substitute - substituteClientID := clienttypes.FormatClientIdentifier(wasmtypes.Wasm, 1) - s.SetupClients(ctx, r, ibc.DefaultClientOpts()) - - // wait for block - err = testutil.WaitForBlocks(ctx, 1, cosmosChain, polkadotChain) - s.Require().NoError(err) - - // ensure subject client is expired - status, err := s.clientStatus(ctx, cosmosChain, subjectClientID) - s.Require().NoError(err) - s.Require().Equal(ibcexported.Expired.String(), status, "unexpected subject client status") - - // ensure substitute client is active - status, err = s.clientStatus(ctx, cosmosChain, substituteClientID) - s.Require().NoError(err) - s.Require().Equal(ibcexported.Active.String(), status, "unexpected substitute client status") - - // create and execute a client recovery proposal - authority, err := s.QueryModuleAccountAddress(ctx, govtypes.ModuleName, cosmosChain) - s.Require().NoError(err) - msgRecoverClient := clienttypes.NewMsgRecoverClient(authority.String(), subjectClientID, substituteClientID) - s.Require().NotNil(msgRecoverClient) - s.ExecuteAndPassGovV1Proposal(ctx, msgRecoverClient, cosmosChain, cosmosUser) - - // ensure subject client is active - status, err = s.clientStatus(ctx, cosmosChain, subjectClientID) - s.Require().NoError(err) - s.Require().Equal(ibcexported.Active.String(), status) - - // ensure substitute client is active - status, err = s.clientStatus(ctx, cosmosChain, substituteClientID) - s.Require().NoError(err) - s.Require().Equal(ibcexported.Active.String(), status) -} - -// extractChecksumFromGzippedContent takes a gzipped wasm contract and returns the checksum. -func (s *GrandpaTestSuite) extractChecksumFromGzippedContent(zippedContent []byte) string { - content, err := wasmtypes.Uncompress(zippedContent, wasmtypes.MaxWasmByteSize()) - s.Require().NoError(err) - - checksum32 := sha256.Sum256(content) - return hex.EncodeToString(checksum32[:]) -} - -// PushNewWasmClientProposal submits a new wasm client governance proposal to the chain. -func (s *GrandpaTestSuite) PushNewWasmClientProposal(ctx context.Context, chain *cosmos.CosmosChain, wallet ibc.Wallet, proposalContentReader io.Reader) string { - zippedContent, err := io.ReadAll(proposalContentReader) - s.Require().NoError(err) - - computedChecksum := s.extractChecksumFromGzippedContent(zippedContent) - - s.Require().NoError(err) - message := wasmtypes.MsgStoreCode{ - Signer: authtypes.NewModuleAddress(govtypes.ModuleName).String(), - WasmByteCode: zippedContent, - } - - s.ExecuteAndPassGovV1Proposal(ctx, &message, chain, wallet) - - checksumBz, err := s.QueryWasmCode(ctx, chain, computedChecksum) - s.Require().NoError(err) - - checksum32 := sha256.Sum256(checksumBz) - actualChecksum := hex.EncodeToString(checksum32[:]) - s.Require().Equal(computedChecksum, actualChecksum, "checksum returned from query did not match the computed checksum") - - return actualChecksum -} - -func (s *GrandpaTestSuite) clientStatus(ctx context.Context, chain ibc.Chain, clientID string) (string, error) { - queryClient := s.GetChainGRCPClients(chain).ClientQueryClient - res, err := queryClient.ClientStatus(ctx, &clienttypes.QueryClientStatusRequest{ - ClientId: clientID, - }) - if err != nil { - return "", err - } - - return res.Status, nil -} - -func (s *GrandpaTestSuite) fundUsers(ctx context.Context, fundAmount int64, polkadotChain ibc.Chain, cosmosChain ibc.Chain) (ibc.Wallet, ibc.Wallet) { - users := interchaintest.GetAndFundTestUsers(s.T(), ctx, "user", fundAmount, polkadotChain, cosmosChain) - polkadotUser, cosmosUser := users[0], users[1] - err := testutil.WaitForBlocks(ctx, 2, polkadotChain, cosmosChain) // Only waiting 1 block is flaky for parachain - s.Require().NoError(err, "cosmos or polkadot chain failed to make blocks") - - // Check balances are correct - amount := math.NewInt(fundAmount) - polkadotUserAmount, err := polkadotChain.GetBalance(ctx, polkadotUser.FormattedAddress(), polkadotChain.Config().Denom) - s.Require().NoError(err) - s.Require().True(polkadotUserAmount.Equal(amount), "Initial polkadot user amount not expected") - - parachainUserAmount, err := polkadotChain.GetBalance(ctx, polkadotUser.FormattedAddress(), "") - s.Require().NoError(err) - s.Require().True(parachainUserAmount.Equal(amount), "Initial parachain user amount not expected") - - cosmosUserAmount, err := cosmosChain.GetBalance(ctx, cosmosUser.FormattedAddress(), cosmosChain.Config().Denom) - s.Require().NoError(err) - s.Require().True(cosmosUserAmount.Equal(amount), "Initial cosmos user amount not expected") - - return polkadotUser, cosmosUser -} - -// validateTestConfig ensures that the given test config is valid for this test suite. -func validateTestConfig() { - tc := testsuite.LoadConfig() - if tc.ActiveRelayer != "hyperspace" { - panic(fmt.Errorf("hyperspace relayer must be specified")) - } -} - -// getConfigOverrides returns configuration overrides that will be applied to the simapp. -func getConfigOverrides() map[string]any { - consensusOverrides := make(testutil.Toml) - blockTime := 5 - blockT := (time.Duration(blockTime) * time.Second).String() - consensusOverrides["timeout_commit"] = blockT - consensusOverrides["timeout_propose"] = blockT - - configTomlOverrides := make(testutil.Toml) - configTomlOverrides["consensus"] = consensusOverrides - configTomlOverrides["log_level"] = "info" - - configFileOverrides := make(map[string]any) - configFileOverrides["config/config.toml"] = configTomlOverrides - return configFileOverrides -} - -// GetGrandpaTestChains returns the configured chains for the grandpa test suite. -func (s *GrandpaTestSuite) GetGrandpaTestChains() (ibc.Chain, ibc.Chain) { - return s.GetChains(func(options *testsuite.ChainOptions) { - // configure chain A (polkadot) - options.ChainASpec.ChainName = composable - options.ChainASpec.Type = "polkadot" - options.ChainASpec.ChainID = "rococo-local" - options.ChainASpec.Name = "composable" - options.ChainASpec.Images = []ibc.DockerImage{ - // TODO: https://github.com/cosmos/ibc-go/issues/4965 - { - Repository: "ghcr.io/misko9/polkadot-node", - Version: "local", - UidGid: "1000:1000", - }, - { - Repository: "ghcr.io/misko9/parachain-node", - Version: "latest", - UidGid: "1000:1000", - }, - } - options.ChainASpec.Bin = "polkadot" - options.ChainASpec.Bech32Prefix = composable - options.ChainASpec.Denom = "uDOT" - options.ChainASpec.GasPrices = "" - options.ChainASpec.GasAdjustment = 0 - options.ChainASpec.TrustingPeriod = "" - options.ChainASpec.CoinType = "354" - - // these values are set by default for our cosmos chains, we need to explicitly remove them here. - options.ChainASpec.ModifyGenesis = nil - options.ChainASpec.ConfigFileOverrides = nil - options.ChainASpec.EncodingConfig = nil - - // configure chain B (cosmos) - options.ChainBSpec.ChainName = simd // Set chain name so that a suffix with a "dash" is not appended (required for hyperspace) - options.ChainBSpec.Type = "cosmos" - options.ChainBSpec.Name = "simd" - options.ChainBSpec.ChainID = simd - options.ChainBSpec.Bin = simd - options.ChainBSpec.Bech32Prefix = "cosmos" - - // TODO: hyperspace relayer assumes a denom of "stake", hard code this here right now. - // https://github.com/cosmos/ibc-go/issues/4964 - options.ChainBSpec.Denom = "stake" - options.ChainBSpec.GasPrices = "0.00stake" - options.ChainBSpec.GasAdjustment = 1 - options.ChainBSpec.TrustingPeriod = "504h" - options.ChainBSpec.CoinType = "118" - - options.ChainBSpec.ChainConfig.NoHostMount = false - options.ChainBSpec.ConfigFileOverrides = getConfigOverrides() - options.ChainBSpec.EncodingConfig = testsuite.SDKEncodingConfig() - }) -} - -// queryAllowedClientsParam queries the on-chain allowed clients param for 02-client -func (s *GrandpaTestSuite) queryAllowedClientsParam(ctx context.Context, chain ibc.Chain) []string { - if testvalues.SelfParamsFeatureReleases.IsSupported(chain.Config().Images[0].Version) { - queryClient := s.GetChainGRCPClients(chain).ClientQueryClient - res, err := queryClient.ClientParams(ctx, &clienttypes.QueryClientParamsRequest{}) - s.Require().NoError(err) - - return res.Params.AllowedClients - } - queryClient := s.GetChainGRCPClients(chain).ParamsQueryClient - res, err := queryClient.Params(ctx, ¶msproposaltypes.QueryParamsRequest{ - Subspace: ibcexported.ModuleName, - Key: string(clienttypes.KeyAllowedClients), - }) - s.Require().NoError(err) - - var allowedClients []string - err = cmtjson.Unmarshal([]byte(res.Param.Value), &allowedClients) - s.Require().NoError(err) - - return allowedClients -} - -// allowWasmClients adds 08-wasm to the on-chain allowed clients param for 02-client -func (s *GrandpaTestSuite) allowWasmClients(ctx context.Context, chain ibc.Chain, wallet ibc.Wallet, allowedClients []string) { - govModuleAddress, err := s.QueryModuleAccountAddress(ctx, govtypes.ModuleName, chain) - s.Require().NoError(err) - s.Require().NotNil(govModuleAddress) - - allowedClients = append(allowedClients, wasmtypes.Wasm) - if testvalues.SelfParamsFeatureReleases.IsSupported(chain.Config().Images[0].Version) { - msg := clienttypes.NewMsgUpdateParams(govModuleAddress.String(), clienttypes.NewParams(allowedClients...)) - s.ExecuteAndPassGovV1Proposal(ctx, msg, chain, wallet) - } else { - value, err := cmtjson.Marshal(allowedClients) - s.Require().NoError(err) - changes := []paramsproposaltypes.ParamChange{ - paramsproposaltypes.NewParamChange(ibcexported.ModuleName, string(clienttypes.KeyAllowedClients), string(value)), - } - - proposal := paramsproposaltypes.NewParameterChangeProposal(ibctesting.Title, ibctesting.Description, changes) - s.ExecuteAndPassGovV1Beta1Proposal(ctx, chain, wallet, proposal) - } -}