mukan-ibc/e2e/testsuite/testsuite.go
Mukan Erkin Törük 88dd97a9f8
Some checks failed
CodeQL / Analyze (push) Waiting to run
golangci-lint / lint (push) Waiting to run
Tests / Code Coverage / build (amd64) (push) Waiting to run
Tests / Code Coverage / build (arm64) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[additional-args:-tags="test_e2e" name:e2e path:./e2e]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:08-wasm path:./modules/light-clients/08-wasm]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:ibc-go path:.]) (push) Waiting to run
Docker Build & Push Simapp (main) / docker-build (push) Has been cancelled
refactor: replace all github.com upstream refs with git.cw.tr/mukan-network
2026-05-11 03:36:22 +03:00

740 lines
30 KiB
Go

package testsuite
import (
"context"
"errors"
"fmt"
"os"
"path"
"slices"
"strings"
"sync"
interchaintest "github.com/cosmos/interchaintest/v10"
"github.com/cosmos/interchaintest/v10/chain/cosmos"
"github.com/cosmos/interchaintest/v10/ibc"
"github.com/cosmos/interchaintest/v10/testreporter"
test "github.com/cosmos/interchaintest/v10/testutil"
mobycli "github.com/moby/moby/client"
testifysuite "github.com/stretchr/testify/suite"
"go.uber.org/zap"
sdkmath "cosmossdk.io/math"
sdk "git.cw.tr/mukan-network/mukan-sdk/types"
banktypes "git.cw.tr/mukan-network/mukan-sdk/x/bank/types"
"github.com/cosmos/ibc-go/e2e/internal/directories"
"github.com/cosmos/ibc-go/e2e/relayer"
"github.com/cosmos/ibc-go/e2e/testsuite/diagnostics"
"github.com/cosmos/ibc-go/e2e/testsuite/query"
transfertypes "git.cw.tr/mukan-network/mukan-ibc/modules/apps/transfer/types"
clienttypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/02-client/types"
channeltypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/04-channel/types"
)
const (
// ChainARelayerName is the name given to the relayer wallet on ChainA
ChainARelayerName = "rlyA"
// ChainBRelayerName is the name given to the relayer wallet on ChainB
ChainBRelayerName = "rlyB"
// DefaultGasValue is the default gas value used to configure tx.Factory
DefaultGasValue = 500_000_0000
)
// E2ETestSuite has methods and functionality which can be shared among all test suites.
type E2ETestSuite struct {
testifysuite.Suite
// proposalIDs keeps track of the active proposal ID for each chain.
proposalIDs map[string]uint64
// chains is a list of chains that are created for the test suite.
// each test suite has a single slice of chains that are used for all individual test
// cases.
chains []ibc.Chain
relayerWallets relayer.Map
logger *zap.Logger
DockerClient *mobycli.Client
network string
// pathNameIndex is the latest index to be used for generating chains
pathNameIndex int64
// testSuiteName is the name of the test suite, used to store chains under the test suite name.
testSuiteName string
testPaths map[string][]string
channels map[string]map[ibc.Chain][]ibc.ChannelOutput
// channelLock ensures concurrent tests are not creating and accessing channels as the same time.
channelLock sync.Mutex
// relayerLock ensures concurrent tests are not accessing the pool of relayers as the same time.
relayerLock sync.Mutex
// relayerPool is a pool of relayers that can be used in tests.
relayerPool []ibc.Relayer
// testRelayerMap is a map of test suite names to relayers that are used in the test suite.
// this is used as a cache after a relayer has been assigned to a test suite.
testRelayerMap map[string]ibc.Relayer
}
// initState populates variables that are used across the test suite.
// note: this should be called only from SetupSuite.
func (s *E2ETestSuite) initState() {
s.initDockerClient()
s.proposalIDs = map[string]uint64{}
s.testPaths = make(map[string][]string)
s.channels = make(map[string]map[ibc.Chain][]ibc.ChannelOutput)
s.relayerPool = []ibc.Relayer{}
s.testRelayerMap = make(map[string]ibc.Relayer)
s.relayerWallets = make(relayer.Map)
// testSuiteName gets populated in the context of SetupSuite and stored as s.T().Name()
// will return the name of the suite and test when called from SetupTest or within the body of tests.
// the chains will be stored under the test suite name, so we need to store this for future use.
s.testSuiteName = s.T().Name()
}
// initDockerClient creates a docker client and populates the network to be used for the test.
func (s *E2ETestSuite) initDockerClient() {
client, network := interchaintest.DockerSetup(s.T())
s.logger = zap.NewExample()
s.DockerClient = client
s.network = network
}
// configureGenesisDebugExport sets, if needed, env variables to enable exporting of Genesis debug files.
func (s *E2ETestSuite) configureGenesisDebugExport() {
tc := LoadConfig()
t := s.T()
cfg := tc.DebugConfig.GenesisDebug
if !cfg.DumpGenesisDebugInfo {
return
}
// Set the export path.
exportPath := cfg.ExportFilePath
// If no path is provided, use the default (e2e/diagnostics/genesis.json).
if exportPath == "" {
e2eDir, err := directories.E2E()
s.Require().NoError(err, "can't get e2edir")
exportPath = path.Join(e2eDir, directories.DefaultGenesisExportPath)
}
if !path.IsAbs(exportPath) {
wd, err := os.Getwd()
s.Require().NoError(err, "can't get working directory")
exportPath = path.Join(wd, exportPath)
}
// These environment variables are set by interchaintest at runtime.
// Reference: https://github.com/cosmos/interchaintest/blob/main/chain/cosmos/cosmos_chain.go
t.Setenv("EXPORT_GENESIS_FILE_PATH", exportPath)
chainName := tc.GetGenesisChainName()
chainIdx, err := tc.GetChainIndex(chainName)
s.Require().NoError(err)
// interchaintest adds a numeric suffix to the chain name, so we do the same.
genesisChainName := fmt.Sprintf("%s-%d", chainName, chainIdx+1)
t.Setenv("EXPORT_GENESIS_CHAIN", genesisChainName)
}
// initializeRelayerPool pre-loads the relayer pool with n relayers.
// this is a workaround due to the restriction on relayer creation during the test
// If/when interchaintest supports relayer creation during tests, this can be made lazy per-test.
func (s *E2ETestSuite) initializeRelayerPool(n int) []ibc.Relayer {
var relayers []ibc.Relayer
for range n {
relayers = append(relayers, relayer.New(s.T(), *LoadConfig().GetActiveRelayerConfig(), s.logger, s.DockerClient, s.network))
}
return relayers
}
// SetupChains creates the chains for the test suite, and also a relayer that is wired up to establish
// connections and channels between the chains.
func (s *E2ETestSuite) SetupChains(ctx context.Context, chainCount int, channelOptionsModifier ChainOptionModifier, chainSpecOpts ...ChainOptionConfiguration) {
s.T().Logf("Setting up %d chains: %s", chainCount, s.T().Name())
if LoadConfig().DebugConfig.KeepContainers {
s.Require().NoError(os.Setenv(KeepContainersEnv, "true"))
}
s.initState()
s.configureGenesisDebugExport()
chainOptions, err := DefaultChainOptions(chainCount)
s.Require().NoError(err)
for _, opt := range chainSpecOpts {
opt(&chainOptions)
}
specCount := len(chainOptions.ChainSpecs)
s.Require().GreaterOrEqualf(specCount, chainCount, "wants to create %d chains, but DefaultChainOptions has %d configurations", chainCount, specCount)
s.chains = s.createChains(chainCount, chainOptions)
s.relayerPool = s.initializeRelayerPool(chainOptions.RelayerCount)
ic := s.newInterchain(s.relayerPool, s.chains, channelOptionsModifier)
buildOpts := interchaintest.InterchainBuildOptions{
TestName: s.T().Name(),
Client: s.DockerClient,
NetworkID: s.network,
// we skip path creation because we are just creating the chains and not connections/channels
SkipPathCreation: true,
}
s.Require().NoError(ic.Build(ctx, s.GetRelayerExecReporter(), buildOpts))
// setup query paths for GRPC queries:
for _, chain := range s.chains {
s.Require().NoError(query.PopulateQueryReqToPath(ctx, chain))
}
}
// CreateDefaultPaths creates a path between the chains using the default client and channel options.
// this should be called as the setup function in most tests if no additional options are required.
func (s *E2ETestSuite) CreateDefaultPaths(testName string) ibc.Relayer {
return s.CreatePaths(ibc.DefaultClientOpts(), DefaultChannelOpts(s.GetAllChains()), testName)
}
// CreatePaths creates paths between the chains using the provided client and channel options.
// The paths are created such that ChainA is connected to ChainB, ChainB is connected to ChainC etc.
func (s *E2ETestSuite) CreatePaths(clientOpts ibc.CreateClientOptions, channelOpts ibc.CreateChannelOptions, testName string) ibc.Relayer {
s.T().Logf("Setting up path for: %s", testName)
if s.channels[testName] == nil {
s.channels[testName] = make(map[ibc.Chain][]ibc.ChannelOutput)
}
r := s.GetRelayerForTest(testName)
ctx := context.TODO()
allChains := s.GetAllChains()
for i := range len(allChains) - 1 {
chainA, chainB := allChains[i], allChains[i+1]
s.CreatePath(ctx, r, chainA, chainB, clientOpts, channelOpts, testName)
}
return r
}
// CreatePath creates a path between chainA and chainB using the provided client and channel options.
func (s *E2ETestSuite) CreatePath(
ctx context.Context,
r ibc.Relayer,
chainA ibc.Chain,
chainB ibc.Chain,
clientOpts ibc.CreateClientOptions,
channelOpts ibc.CreateChannelOptions,
testName string,
) (chainAChannel ibc.ChannelOutput, chainBChannel ibc.ChannelOutput) {
pathName := s.generatePathName()
s.testPaths[testName] = append(s.testPaths[testName], pathName)
s.T().Logf("establishing path between %s and %s on path %s", chainA.Config().ChainID, chainB.Config().ChainID, pathName)
err := r.GeneratePath(ctx, s.GetRelayerExecReporter(), chainA.Config().ChainID, chainB.Config().ChainID, pathName)
s.Require().NoError(err)
// Create new clients
err = r.CreateClients(ctx, s.GetRelayerExecReporter(), pathName, clientOpts)
s.Require().NoError(err)
err = test.WaitForBlocks(ctx, 1, chainA, chainB)
s.Require().NoError(err)
err = r.CreateConnections(ctx, s.GetRelayerExecReporter(), pathName)
s.Require().NoError(err)
err = test.WaitForBlocks(ctx, 1, chainA, chainB)
s.Require().NoError(err)
s.createChannelWithLock(ctx, r, pathName, testName, channelOpts, chainA, chainB)
aChannels := s.channels[testName][chainA]
bChannels := s.channels[testName][chainB]
return aChannels[len(aChannels)-1], bChannels[len(bChannels)-1]
}
// createChannelWithLock creates a channel between the two provided chains for the given test name. This applies a lock
// to ensure that the channels that are created are correctly mapped to the test that created them.
func (s *E2ETestSuite) createChannelWithLock(ctx context.Context, r ibc.Relayer, pathName, testName string, channelOpts ibc.CreateChannelOptions, chainA, chainB ibc.Chain) {
// NOTE: we need to lock the creation of channels and applying of packet filters, as if we don't, the result
// of `r.GetChannels` may return channels created by other relayers in different tests.
s.channelLock.Lock()
defer s.channelLock.Unlock()
err := r.CreateChannel(ctx, s.GetRelayerExecReporter(), pathName, channelOpts)
s.Require().NoError(err)
err = test.WaitForBlocks(ctx, 1, chainA, chainB)
s.Require().NoError(err)
for _, c := range []ibc.Chain{chainA, chainB} {
channels, err := r.GetChannels(ctx, s.GetRelayerExecReporter(), c.Config().ChainID)
s.Require().NoError(err)
if _, ok := s.channels[testName][c]; !ok {
s.channels[testName][c] = []ibc.ChannelOutput{}
}
// keep track of channels associated with a given chain for access within the tests.
// only the most recent channel is relevant.
s.channels[testName][c] = append(s.channels[testName][c], getLatestChannel(channels))
err = relayer.ApplyPacketFilter(ctx, s.T(), r, c.Config().ChainID, s.channels[testName][c])
s.Require().NoError(err, "failed to watch port and channel on chain: %s", c.Config().ChainID)
}
}
// getLatestChannel returns the latest channel from the list of channels.
func getLatestChannel(channels []ibc.ChannelOutput) ibc.ChannelOutput {
return slices.MaxFunc(channels, func(a, b ibc.ChannelOutput) int {
seqA, _ := channeltypes.ParseChannelSequence(a.ChannelID)
seqB, _ := channeltypes.ParseChannelSequence(b.ChannelID)
return int(seqA - seqB)
})
}
// GetChainAChannelForTest returns the ibc.ChannelOutput for the current test.
// this defaults to the first entry in the list, and will be what is needed in the case of
// a single channel test.
func (s *E2ETestSuite) GetChainAChannelForTest(testName string) ibc.ChannelOutput {
return s.GetChannelsForTest(s.GetAllChains()[0], testName)[0]
}
// GetChannelsForTest returns all channels for the specified test.
func (s *E2ETestSuite) GetChannelsForTest(chain ibc.Chain, testName string) []ibc.ChannelOutput {
channels, ok := s.channels[testName][chain]
s.Require().True(ok, "channel not found for test %s", testName)
return channels
}
// GetRelayerForTest returns the relayer for the current test from the available pool of relayers.
// once a relayer has been returned to a test, it is cached and will be reused for the duration of the test.
func (s *E2ETestSuite) GetRelayerForTest(testName string) ibc.Relayer {
s.relayerLock.Lock()
defer s.relayerLock.Unlock()
if r, ok := s.testRelayerMap[testName]; ok {
s.T().Logf("relayer already created for test: %s", testName)
return r
}
if len(s.relayerPool) == 0 {
panic(errors.New("relayer pool is empty"))
}
r := s.relayerPool[0]
// remove the relayer from the pool
s.relayerPool = s.relayerPool[1:]
s.testRelayerMap[testName] = r
return r
}
// GetRelayerUsers returns two ibc.Wallet instances which can be used for the relayer users
// on the two chains.
func (s *E2ETestSuite) GetRelayerUsers(ctx context.Context, testName string) (ibc.Wallet, ibc.Wallet) {
chains := s.GetAllChains()
chainA, chainB := chains[0], chains[1]
rlyAName := fmt.Sprintf("%s-%s", ChainARelayerName, testName)
rlyBName := fmt.Sprintf("%s-%s", ChainBRelayerName, testName)
chainAAccountBytes, err := chainA.GetAddress(ctx, rlyAName)
s.Require().NoError(err)
chainBAccountBytes, err := chainB.GetAddress(ctx, rlyBName)
s.Require().NoError(err)
chainARelayerUser := cosmos.NewWallet(rlyAName, chainAAccountBytes, "", chainA.Config())
chainBRelayerUser := cosmos.NewWallet(rlyBName, chainBAccountBytes, "", chainB.Config())
s.relayerWallets.AddRelayer(testName, chainARelayerUser)
s.relayerWallets.AddRelayer(testName, chainBRelayerUser)
return chainARelayerUser, chainBRelayerUser
}
// ChainOptionModifier is a function which accepts 2 chains as inputs, and returns a channel creation modifier function
// in order to conditionally modify the channel options based on the chains being used.
type ChainOptionModifier func(chainA, chainB ibc.Chain) func(options *ibc.CreateChannelOptions)
// newInterchain constructs a new interchain instance that creates channels between the chains.
func (s *E2ETestSuite) newInterchain(relayers []ibc.Relayer, chains []ibc.Chain, modificationProvider ChainOptionModifier) *interchaintest.Interchain {
ic := interchaintest.NewInterchain()
for _, chain := range chains {
ic.AddChain(chain)
}
for i, r := range relayers {
ic.AddRelayer(r, fmt.Sprintf("r-%d", i))
}
// iterate through all chains, and create links such that there is a channel between
// - chainA and chainB
// - chainB and chainC
// - chainC and chainD etc
for i := range len(chains) - 1 {
pathName := s.generatePathName()
channelOpts := DefaultChannelOpts(chains)
chain1, chain2 := chains[i], chains[i+1]
if modificationProvider != nil {
// make a modification to the channel options based on the chains which are being used.
modificationFn := modificationProvider(chain1, chain2)
modificationFn(&channelOpts)
}
for _, r := range relayers {
ic.AddLink(interchaintest.InterchainLink{
Chain1: chains[i],
Chain2: chains[i+1],
Relayer: r,
Path: pathName,
CreateChannelOpts: channelOpts,
})
}
}
return ic
}
// generatePathName generates the path name using the test suites name
func (s *E2ETestSuite) generatePathName() string {
pathName := GetPathName(s.pathNameIndex)
s.pathNameIndex++
return pathName
}
func (s *E2ETestSuite) GetPaths(testName string) []string {
paths, ok := s.testPaths[testName]
s.Require().True(ok, "paths not found for test %s", testName)
return paths
}
// GetPathName returns the name of a path at a specific index. This can be used in tests
// when the path name is required.
func GetPathName(idx int64) string {
pathName := fmt.Sprintf("path-%d", idx)
return strings.ReplaceAll(pathName, "/", "-")
}
// generatePath generates the path name using the test suites name. The indices provided specify which chains should be
// used. E.g. to generate a path between chain A and B, you would use 0 and 1, to specify between A and C, you would
// use 0 and 2 etc.
func (s *E2ETestSuite) generatePath(ctx context.Context, ibcrelayer ibc.Relayer, chainAIdx, chainBIdx int) string {
chains := s.GetAllChains()
chainA, chainB := chains[chainAIdx], chains[chainBIdx]
chainAID := chainA.Config().ChainID
chainBID := chainB.Config().ChainID
pathName := s.generatePathName()
err := ibcrelayer.GeneratePath(ctx, s.GetRelayerExecReporter(), chainAID, chainBID, pathName)
s.Require().NoError(err)
return pathName
}
// SetupClients creates clients on chainA and chainB using the provided create client options
func (s *E2ETestSuite) SetupClients(ctx context.Context, ibcrelayer ibc.Relayer, opts ibc.CreateClientOptions) {
pathName := s.generatePath(ctx, ibcrelayer, 0, 1)
err := ibcrelayer.CreateClients(ctx, s.GetRelayerExecReporter(), pathName, opts)
s.Require().NoError(err)
}
// UpdateClients updates clients on chainA and chainB
func (s *E2ETestSuite) UpdateClients(ctx context.Context, ibcrelayer ibc.Relayer, pathName string) {
err := ibcrelayer.UpdateClients(ctx, s.GetRelayerExecReporter(), pathName)
s.Require().NoError(err)
}
// GetChains returns two chains that can be used in a test. The pair returned
// is unique to the current test being run. Note: this function does not create containers.
func (s *E2ETestSuite) GetChains() (ibc.Chain, ibc.Chain) {
chains := s.GetAllChains()
return chains[0], chains[1]
}
// GetAllChains returns all chains that can be used in a test. The chains returned
// are unique to the current test being run. Note: this function does not create containers.
func (s *E2ETestSuite) GetAllChains() []ibc.Chain {
// chains are stored on a per test suite level
chains := s.chains
s.Require().NotEmpty(chains, "chains not found for test %s", s.testSuiteName)
return chains
}
// GetRelayerWallets returns the ibcrelayer wallets associated with the chains.
func (s *E2ETestSuite) GetRelayerWallets(ibcrelayer ibc.Relayer) (ibc.Wallet, ibc.Wallet, error) {
chains := s.GetAllChains()
chainA, chainB := chains[0], chains[1]
chainARelayerWallet, ok := ibcrelayer.GetWallet(chainA.Config().ChainID)
if !ok {
return nil, nil, errors.New("unable to find chain A relayer wallet")
}
chainBRelayerWallet, ok := ibcrelayer.GetWallet(chainB.Config().ChainID)
if !ok {
return nil, nil, errors.New("unable to find chain B relayer wallet")
}
return chainARelayerWallet, chainBRelayerWallet, nil
}
// RecoverRelayerWallets adds the corresponding ibcrelayer address to the keychain of the chain.
// This is useful if commands executed on the chains expect the relayer information to present in the keychain.
func (s *E2ETestSuite) RecoverRelayerWallets(ctx context.Context, ibcrelayer ibc.Relayer, testName string) (ibc.Wallet, ibc.Wallet, error) {
chainARelayerWallet, chainBRelayerWallet, err := s.GetRelayerWallets(ibcrelayer)
if err != nil {
return nil, nil, err
}
chains := s.GetAllChains()
chainA, chainB := chains[0], chains[1]
rlyAName := fmt.Sprintf("%s-%s", ChainARelayerName, testName)
rlyBName := fmt.Sprintf("%s-%s", ChainBRelayerName, testName)
if err := chainA.RecoverKey(ctx, rlyAName, chainARelayerWallet.Mnemonic()); err != nil {
return nil, nil, fmt.Errorf("could not recover relayer wallet on chain A: %s", err)
}
if err := chainB.RecoverKey(ctx, rlyBName, chainBRelayerWallet.Mnemonic()); err != nil {
return nil, nil, fmt.Errorf("could not recover relayer wallet on chain B: %s", err)
}
return chainARelayerWallet, chainBRelayerWallet, nil
}
// StartRelayer starts the given ibcrelayer.
func (s *E2ETestSuite) StartRelayer(r ibc.Relayer, testName string) {
s.Require().NoError(r.StartRelayer(context.TODO(), s.GetRelayerExecReporter(), s.GetPaths(testName)...), "failed to start relayer")
chains := s.GetAllChains()
var chainHeighters []test.ChainHeighter
for _, c := range chains {
chainHeighters = append(chainHeighters, c)
}
// wait for every chain to produce some blocks before using the relayer.
s.Require().NoError(test.WaitForBlocks(context.TODO(), 10, chainHeighters...), "failed to wait for blocks")
}
// StopRelayer stops the given ibcrelayer.
func (s *E2ETestSuite) StopRelayer(ctx context.Context, ibcrelayer ibc.Relayer) {
err := ibcrelayer.StopRelayer(ctx, s.GetRelayerExecReporter())
s.Require().NoError(err)
}
// RestartRelayer restarts the given relayer.
func (s *E2ETestSuite) RestartRelayer(ctx context.Context, ibcrelayer ibc.Relayer, testName string) {
s.StopRelayer(ctx, ibcrelayer)
s.StartRelayer(ibcrelayer, testName)
}
// CreateUserOnChainA creates a user with the given amount of funds on chain A.
func (s *E2ETestSuite) CreateUserOnChainA(ctx context.Context, amount int64) ibc.Wallet {
return s.createWalletOnChainIndex(ctx, amount, 0)
}
// CreateUserOnChainB creates a user with the given amount of funds on chain B.
func (s *E2ETestSuite) CreateUserOnChainB(ctx context.Context, amount int64) ibc.Wallet {
return s.createWalletOnChainIndex(ctx, amount, 1)
}
// CreateUserOnChainC creates a user with the given amount of funds on chain C.
func (s *E2ETestSuite) CreateUserOnChainC(ctx context.Context, amount int64) ibc.Wallet {
return s.createWalletOnChainIndex(ctx, amount, 2)
}
// createWalletOnChainIndex creates a wallet with the given amount of funds on the chain of the given index.
func (s *E2ETestSuite) createWalletOnChainIndex(ctx context.Context, amount, chainIndex int64) ibc.Wallet {
chain := s.GetAllChains()[chainIndex]
wallet := interchaintest.GetAndFundTestUsers(s.T(), ctx, strings.ReplaceAll(s.T().Name(), " ", "-"), sdkmath.NewInt(amount), chain)[0]
// note the GetAndFundTestUsers requires the caller to wait for some blocks before the funds are accessible.
s.Require().NoError(test.WaitForBlocks(ctx, 2, chain))
return wallet
}
// GetChainANativeBalance gets the balance of a given user on chain A.
func (s *E2ETestSuite) GetChainANativeBalance(ctx context.Context, user ibc.Wallet) (int64, error) {
chainA := s.GetAllChains()[0]
return GetChainBalanceForDenom(ctx, chainA, chainA.Config().Denom, user)
}
// GetChainBNativeBalance gets the balance of a given user on chain B.
func (s *E2ETestSuite) GetChainBNativeBalance(ctx context.Context, user ibc.Wallet) (int64, error) {
chainB := s.GetAllChains()[1]
return GetChainBalanceForDenom(ctx, chainB, chainB.Config().Denom, user)
}
// GetChainBalanceForDenom returns the balance for a given denom given a chain.
func GetChainBalanceForDenom(ctx context.Context, chain ibc.Chain, denom string, user ibc.Wallet) (int64, error) {
balanceResp, err := query.GRPCQuery[banktypes.QueryBalanceResponse](ctx, chain, &banktypes.QueryBalanceRequest{
Address: user.FormattedAddress(),
Denom: denom,
})
if err != nil {
return 0, err
}
return balanceResp.Balance.Amount.Int64(), nil
}
// AssertPacketRelayed asserts that the packet commitment does not exist on the sending chain.
// The packet commitment will be deleted upon a packet acknowledgement or timeout.
func (s *E2ETestSuite) AssertPacketRelayed(ctx context.Context, chain ibc.Chain, portID, channelID string, sequence uint64) {
_, err := query.GRPCQuery[channeltypes.QueryPacketCommitmentResponse](ctx, chain, &channeltypes.QueryPacketCommitmentRequest{
PortId: portID,
ChannelId: channelID,
Sequence: sequence,
})
s.Require().ErrorContains(err, "packet commitment hash not found")
}
// AssertPacketAcknowledged asserts that the packet has been acknowledged on the specified chain.
func (s *E2ETestSuite) AssertPacketAcknowledged(ctx context.Context, chain ibc.Chain, portID, channelID string, sequence uint64) {
_, err := query.GRPCQuery[channeltypes.QueryPacketAcknowledgementResponse](ctx, chain, &channeltypes.QueryPacketAcknowledgementRequest{
PortId: portID,
ChannelId: channelID,
Sequence: sequence,
})
s.Require().NoError(err)
}
// AssertHumanReadableDenom asserts that a human readable denom is present for a given chain.
func (s *E2ETestSuite) AssertHumanReadableDenom(ctx context.Context, chain ibc.Chain, counterpartyNativeDenom string, counterpartyChannel ibc.ChannelOutput) {
chainIBCDenom := GetIBCToken(counterpartyNativeDenom, counterpartyChannel.Counterparty.PortID, counterpartyChannel.Counterparty.ChannelID)
denomMetadataResp, err := query.GRPCQuery[banktypes.QueryDenomMetadataResponse](ctx, chain, &banktypes.QueryDenomMetadataRequest{
Denom: chainIBCDenom.IBCDenom(),
})
s.Require().NoError(err)
denomMetadata := denomMetadataResp.Metadata
s.Require().Equal(chainIBCDenom.IBCDenom(), denomMetadata.Base, "denom metadata base does not match expected %s: got %s", chainIBCDenom.IBCDenom(), denomMetadata.Base)
expectedName := fmt.Sprintf("%s/%s/%s IBC token", counterpartyChannel.Counterparty.PortID, counterpartyChannel.Counterparty.ChannelID, counterpartyNativeDenom)
s.Require().Equal(expectedName, denomMetadata.Name, "denom metadata name does not match expected %s: got %s", expectedName, denomMetadata.Name)
expectedDisplay := fmt.Sprintf("%s/%s/%s", counterpartyChannel.Counterparty.PortID, counterpartyChannel.Counterparty.ChannelID, counterpartyNativeDenom)
s.Require().Equal(expectedDisplay, denomMetadata.Display, "denom metadata display does not match expected %s: got %s", expectedDisplay, denomMetadata.Display)
s.Require().Equal(strings.ToUpper(counterpartyNativeDenom), denomMetadata.Symbol, "denom metadata symbol does not match expected %s: got %s", strings.ToUpper(counterpartyNativeDenom), denomMetadata.Symbol)
}
// createChains creates two separate chains in docker containers.
// test and can be retrieved with GetChains.
func (s *E2ETestSuite) createChains(chainCount int, chainOptions ChainOptions) []ibc.Chain {
t := s.T()
s.Require().GreaterOrEqualf(len(chainOptions.ChainSpecs), chainCount, "len(chainOptions.ChainSpecs): %d < chainCount: %d", len(chainOptions.ChainSpecs), chainCount)
cf := interchaintest.NewBuiltinChainFactory(s.logger, chainOptions.ChainSpecs[:chainCount]) // Take the first N specs
// this is intentionally called after the interchaintest.DockerSetup function. The above function registers a
// cleanup task which deletes all containers. By registering a cleanup function afterwards, it is executed first
// this allows us to process the logs before the containers are removed.
t.Cleanup(func() {
dumpLogs := LoadConfig().DebugConfig.DumpLogs
var chainNames []string
for _, chain := range chainOptions.ChainSpecs {
chainNames = append(chainNames, chain.Name)
}
diagnostics.Collect(t, s.DockerClient, dumpLogs, s.testSuiteName, chainNames...)
})
chains, err := cf.Chains(t.Name())
s.Require().NoError(err)
// initialise proposal ids for all chains.
for _, chain := range chains {
s.proposalIDs[chain.Config().ChainID] = 1
}
return chains
}
// GetRelayerExecReporter returns a testreporter.RelayerExecReporter instances
// using the current test's testing.T.
func (s *E2ETestSuite) GetRelayerExecReporter() *testreporter.RelayerExecReporter {
rep := testreporter.NewNopReporter()
return rep.RelayerExecReporter(s.T())
}
// TransferChannelOptions configures both of the chains to have non-incentivized transfer channels.
func (*E2ETestSuite) TransferChannelOptions() ibc.CreateChannelOptions {
opts := ibc.DefaultChannelOpts()
opts.Version = transfertypes.V1
return opts
}
// GetTimeoutHeight returns a timeout height of 1000 blocks above the current block height.
// This function should be used when the timeout is never expected to be reached
func (s *E2ETestSuite) GetTimeoutHeight(ctx context.Context, chain ibc.Chain) clienttypes.Height {
height, err := chain.Height(ctx)
s.Require().NoError(err)
return clienttypes.NewHeight(clienttypes.ParseChainID(chain.Config().ChainID), uint64(height)+1000)
}
// GetIBCToken returns the denomination of the full token denom sent to the receiving channel
func GetIBCToken(fullTokenDenom string, portID, channelID string) transfertypes.Denom {
return transfertypes.ExtractDenomFromPath(fmt.Sprintf("%s/%s/%s", portID, channelID, fullTokenDenom))
}
// getValidatorsAndFullNodes returns the number of validators and full nodes respectively that should be used for
// the test. If the test is running in CI, more nodes are used, when running locally a single node is used by default to
// use less resources and allow the tests to run faster.
// both the number of validators and full nodes can be overwritten in a config file.
func getValidatorsAndFullNodes(chainIdx int) (int, int) {
tc := LoadConfig()
return tc.GetChainNumValidators(chainIdx), tc.GetChainNumFullNodes(chainIdx)
}
// GetMsgTransfer returns a MsgTransfer that is constructed based on the channel version
func GetMsgTransfer(portID, channelID, version string, token sdk.Coin, sender, receiver string, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, memo string) *transfertypes.MsgTransfer {
var msg *transfertypes.MsgTransfer
switch version {
case transfertypes.V1:
msg = &transfertypes.MsgTransfer{
SourcePort: portID,
SourceChannel: channelID,
Token: token,
Sender: sender,
Receiver: receiver,
TimeoutHeight: timeoutHeight,
TimeoutTimestamp: timeoutTimestamp,
Memo: memo,
}
default:
panic(fmt.Errorf("unsupported transfer version: %s", version))
}
return msg
}
// SuiteName returns the name of the test suite.
func (s *E2ETestSuite) SuiteName() string {
return s.testSuiteName
}
// ThreeChainSetup provides the default behaviour to wire up 3 chains in the tests.
func ThreeChainSetup() ChainOptionConfiguration {
// copy all values of existing chains and tweak to make unique to new chain.
return func(options *ChainOptions) {
chainCSpec := *options.ChainSpecs[0] // nolint
chainCSpec.ChainID = "chainC-1"
chainCSpec.Name = "simapp-c"
options.ChainSpecs = append(options.ChainSpecs, &chainCSpec)
}
}
// DefaultChannelOpts returns the default chain options for the test suite based on the provided chains.
func DefaultChannelOpts(chains []ibc.Chain) ibc.CreateChannelOptions {
channelOptions := ibc.DefaultChannelOpts()
channelOptions.Version = transfertypes.V1
return channelOptions
}