Sfoglia il codice sorgente

chore: add cw wormhole interchain tests (#4189)

* wormchain: cw_wormhole ict start

* wormchain(interchaintest): initial test implementation

* Add Additional Core Contract ICTs

* Finish Core Contract VAA ICTs

* Add Replay Protection Checks

* Extract Helper Commands

* Fix Modify Genesis

* Hook into ICT Workflow

* Add Missing UpgradeContract VAA Test

* Add Additional Fee Checks

---------

Co-authored-by: Kaku <105181329+kakucodes@users.noreply.github.com>
Co-authored-by: Obie Kaku <obiekaku@gmail.com>
Joel Smith 10 mesi fa
parent
commit
4f0e46f6bf

+ 1 - 0
.github/workflows/wormchain-icts.yml

@@ -30,6 +30,7 @@ jobs:
           - "ictest-upgrade"
           - "ictest-wormchain"
           - "ictest-ibc-receiver"
+          - "ictest-cw-wormhole"
       fail-fast: false
 
     steps:

+ 4 - 1
wormchain/Makefile

@@ -105,4 +105,7 @@ ictest-wormchain: rm-testcache
 ictest-ibc-receiver: rm-testcache
 	cd interchaintest && go test -race -v -run ^TestIbcReceiver ./...
 
-.PHONY: ictest-cancel-upgrade ictest-malformed-payload ictest-upgrade-failure ictest-upgrade ictest-wormchain ictest-ibc-receiver 
+ictest-cw-wormhole: rm-testcache
+	cd interchaintest && go test -race -v -run ^TestCWWormhole ./...
+
+.PHONY: ictest-cancel-upgrade ictest-malformed-payload ictest-upgrade-failure ictest-upgrade ictest-wormchain ictest-ibc-receiver ictest-cw-wormhole

BIN
wormchain/interchaintest/contracts/cw_wormhole.wasm


+ 492 - 0
wormchain/interchaintest/cw_wormhole_test.go

@@ -0,0 +1,492 @@
+package ictest
+
+import (
+	"bytes"
+	"context"
+	"encoding/base64"
+	"encoding/hex"
+	"encoding/json"
+	"testing"
+
+	"github.com/docker/docker/client"
+	"github.com/strangelove-ventures/interchaintest/v4"
+	"github.com/strangelove-ventures/interchaintest/v4/chain/cosmos"
+	"github.com/strangelove-ventures/interchaintest/v4/ibc"
+	"github.com/strangelove-ventures/interchaintest/v4/testutil"
+	"go.uber.org/zap/zaptest"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/wormhole-foundation/wormchain/interchaintest/guardians"
+	"github.com/wormhole-foundation/wormchain/interchaintest/helpers"
+	"github.com/wormhole-foundation/wormchain/interchaintest/helpers/cw_wormhole"
+	"github.com/wormhole-foundation/wormhole/sdk/vaa"
+)
+
+func createSingleNodeCluster(t *testing.T, wormchainVersion string, guardians guardians.ValSet) ibc.Chain {
+	numWormchainVals := len(guardians.Vals)
+	numFullNodes := 0
+
+	wormchainConfig.Images[0].Version = wormchainVersion
+	wormchainConfig.ModifyGenesis = ModifyGenesis(votingPeriod, maxDepositPeriod, guardians, true)
+
+	cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{
+		{
+			ChainName:     "wormchain",
+			ChainConfig:   wormchainConfig,
+			NumValidators: &numWormchainVals,
+			NumFullNodes:  &numFullNodes,
+		},
+	})
+
+	// Get chains from the chain factory
+	chains, err := cf.Chains(t.Name())
+	require.NoError(t, err)
+
+	return chains[0]
+}
+
+func buildSingleNodeInterchain(t *testing.T, chain ibc.Chain) (context.Context, *client.Client) {
+	ic := interchaintest.NewInterchain()
+	ic.AddChain(chain)
+
+	ctx := context.Background()
+	client, network := interchaintest.DockerSetup(t)
+
+	err := ic.Build(ctx, nil, interchaintest.InterchainBuildOptions{
+		TestName:         t.Name(),
+		Client:           client,
+		NetworkID:        network,
+		SkipPathCreation: true,
+	})
+	require.NoError(t, err)
+
+	t.Cleanup(func() {
+		_ = ic.Close()
+	})
+
+	return ctx, client
+}
+
+// TestCWWormholeQueries tests the query functions of the cw_wormhole contract
+func TestCWWormholeQueries(t *testing.T) {
+	// Base setup
+	numVals := 1
+	guardians := guardians.CreateValSet(t, numVals)
+
+	chain := createSingleNodeCluster(t, "v2.24.2", *guardians)
+	ctx, _ := buildSingleNodeInterchain(t, chain)
+
+	wormchain := chain.(*cosmos.CosmosChain)
+
+	// Instantiate the cw_wormhole contract
+	coreInstantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, vaa.ChainIDWormchain, guardians)
+	wormchainCoreContractInfo := helpers.StoreAndInstantiateWormholeContract(t, ctx, wormchain, "faucet", "./contracts/cw_wormhole.wasm", "wormhole_core", coreInstantiateMsg, guardians)
+	contractAddr := wormchainCoreContractInfo.Address
+
+	// Query the contract to check that the guardian set is correct
+	var guardianSetResp cw_wormhole.GuardianSetQueryResponse
+	err := wormchain.QueryContract(ctx, contractAddr, cw_wormhole.QueryMsg{
+		GuardianSetInfo: &cw_wormhole.QueryMsg_GuardianSetInfo{},
+	}, &guardianSetResp)
+	require.NoError(t, err)
+	require.Equal(t, numVals, len(guardianSetResp.Data.Addresses), "guardian set should have the correct number of guardians")
+	// Check that all the guardians from the query are the ones in the running valset
+	for _, val := range guardians.Vals {
+		found := false
+		for _, guardian := range guardianSetResp.Data.Addresses {
+			decoded, err := base64.StdEncoding.DecodeString(string(guardian.Bytes))
+			require.NoError(t, err)
+			guardianDecodedBytes := []byte(decoded)
+			if bytes.Equal(val.Addr, guardianDecodedBytes) {
+				found = true
+				break
+			}
+		}
+		require.True(t, found, "guardian not found in guardian set")
+	}
+
+	// Check that the core contract fee is set to 0uworm
+	var stateResp cw_wormhole.GetStateQueryResponse
+	err = wormchain.QueryContract(ctx, contractAddr, cw_wormhole.QueryMsg{
+		GetState: &cw_wormhole.QueryMsg_GetState{},
+	}, &stateResp)
+	require.NoError(t, err)
+	require.Equal(t, "uworm", stateResp.Data.Fee.Denom, "core contract fee should be in uworm")
+	require.Equal(t, cw_wormhole.Uint128("0"), stateResp.Data.Fee.Amount, "core contract fee should be 0")
+
+	// Check that hex addresse are able to be queried
+	var hexAddressResp cw_wormhole.QueryAddressHexQueryResponse
+	err = wormchain.QueryContract(ctx, contractAddr, cw_wormhole.QueryMsg{
+		QueryAddressHex: &cw_wormhole.QueryMsg_QueryAddressHex{
+			Address: "wormhole14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9srrg465",
+		},
+	}, &hexAddressResp)
+	require.NoError(t, err)
+	require.IsType(t, "", hexAddressResp.Data.Hex, "hex address should be a string")
+
+	// Check that the core contract can properly verify a VAA
+	guardianSetIndex := helpers.QueryConsensusGuardianSetIndex(t, wormchain, ctx)
+	vaa := helpers.GenerateGovernanceVaa(uint32(guardianSetIndex), guardians, []byte("test"))
+	vaaBz, err := vaa.Marshal()
+	require.NoError(t, err)
+	encodedVaa := base64.StdEncoding.EncodeToString(vaaBz)
+	vaaBinary := cw_wormhole.Binary(encodedVaa)
+
+	currentWormchainBlock, err := wormchain.Height(ctx)
+	require.NoError(t, err)
+
+	var parsedVaaResponse cw_wormhole.VerifyVAAQueryResponse
+	err = wormchain.QueryContract(ctx, contractAddr, cw_wormhole.QueryMsg{
+		VerifyVaa: &cw_wormhole.QueryMsg_VerifyVAA{
+			BlockTime: int(currentWormchainBlock),
+			Vaa:       vaaBinary,
+		},
+	}, &parsedVaaResponse)
+	require.NoError(t, err)
+	require.NotNil(t, parsedVaaResponse.Data, "VAA should be verified")
+	require.Equal(t, "test", string(parsedVaaResponse.Data.Payload), "VAA payload should be what we passed in")
+}
+
+// TestCWWormholePostMessage tests the PostMessage function of the cw_wormhole contract
+func TestCWWormholePostMessage(t *testing.T) {
+	numVals := 1
+	guardians := guardians.CreateValSet(t, numVals)
+	chain := createSingleNodeCluster(t, "v2.24.2", *guardians)
+	ctx, _ := buildSingleNodeInterchain(t, chain)
+	wormchain := chain.(*cosmos.CosmosChain)
+
+	// Instantiate contract
+	coreInstantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, vaa.ChainIDWormchain, guardians)
+	contractInfo := helpers.StoreAndInstantiateWormholeContract(t, ctx, wormchain, "faucet", "./contracts/cw_wormhole.wasm", "wormhole_core", coreInstantiateMsg, guardians)
+	contractAddr := contractInfo.Address
+
+	// Create message and encode to base64
+	message := []byte("test message")
+	messageBase64 := base64.StdEncoding.EncodeToString(message)
+	nonce := 1
+
+	executeMsg, err := json.Marshal(cw_wormhole.ExecuteMsg{
+		PostMessage: &cw_wormhole.ExecuteMsg_PostMessage{
+			Message: cw_wormhole.Binary(messageBase64),
+			Nonce:   nonce,
+		},
+	})
+	require.NoError(t, err)
+
+	// Execute contract
+	txHash, err := wormchain.ExecuteContract(ctx, "faucet", contractAddr, string(executeMsg))
+	require.NoError(t, err)
+
+	// Wait 2 blocks
+	err = testutil.WaitForBlocks(ctx, 2, wormchain)
+	require.NoError(t, err)
+
+	// Query and parse the response
+	txResult, _, err := wormchain.Validators[0].ExecQuery(ctx, "tx", txHash)
+	require.NoError(t, err)
+
+	var txResponse cw_wormhole.TxResponse
+	err = json.Unmarshal(txResult, &txResponse)
+	require.NoError(t, err)
+
+	// Verify event attributes
+	cw_wormhole.VerifyEventAttributes(t, &txResponse, map[string]string{
+		"_contract_address": contractAddr,
+		"message.message":   hex.EncodeToString(message),
+		"message.nonce":     "1",
+		"message.sequence":  "0",
+	})
+}
+
+// TestCWWormholeUpdateGuardianSet tests the UpdateGuardianSet function of the cw_wormhole contract
+func TestCWWormholeUpdateGuardianSet(t *testing.T) {
+	// Setup chain and contract
+	numVals := 1
+	oldGuardians := guardians.CreateValSet(t, numVals)
+	chain := createSingleNodeCluster(t, "v2.24.2", *oldGuardians)
+	ctx, _ := buildSingleNodeInterchain(t, chain)
+	wormchain := chain.(*cosmos.CosmosChain)
+
+	// Deploy contract
+	coreInstantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, vaa.ChainIDWormchain, oldGuardians)
+	contractInfo := helpers.StoreAndInstantiateWormholeContract(t, ctx, wormchain, "faucet", "./contracts/cw_wormhole.wasm", "wormhole_core", coreInstantiateMsg, oldGuardians)
+	contractAddr := contractInfo.Address
+
+	// Get initial guardian set index
+	initialIndex := int(helpers.QueryConsensusGuardianSetIndex(t, wormchain, ctx))
+	signingGuardians := guardians.CreateValSet(t, numVals+1)
+
+	t.Run("successful update", func(t *testing.T) {
+		newGuardians := signingGuardians
+		err := cw_wormhole.SubmitGuardianSetUpdate(t, ctx, wormchain, contractAddr, newGuardians, uint32(initialIndex+1), oldGuardians)
+		require.NoError(t, err)
+		cw_wormhole.VerifyGuardianSet(t, ctx, wormchain, contractAddr, newGuardians, initialIndex+1)
+	})
+
+	t.Run("invalid guardian set index", func(t *testing.T) {
+		// Try to update with non-sequential index
+		newGuardians := guardians.CreateValSet(t, numVals+1)
+		err := cw_wormhole.SubmitGuardianSetUpdate(t, ctx, wormchain, contractAddr, newGuardians, uint32(initialIndex+3), signingGuardians)
+		require.Error(t, err)
+
+		// Try to update with same index
+		err = cw_wormhole.SubmitGuardianSetUpdate(t, ctx, wormchain, contractAddr, newGuardians, uint32(initialIndex), signingGuardians)
+		require.Error(t, err)
+	})
+
+	t.Run("empty guardian set", func(t *testing.T) {
+		emptyGuardians := guardians.CreateValSet(t, 0)
+		err := cw_wormhole.SubmitGuardianSetUpdate(t, ctx, wormchain, contractAddr, emptyGuardians, uint32(initialIndex+1), signingGuardians)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "GuardianSignatureError")
+	})
+
+	t.Run("duplicate guardians", func(t *testing.T) {
+		// Create guardian set with duplicate addresses
+		dupGuardians := guardians.CreateValSet(t, 1)
+		dupGuardians.Vals = append(dupGuardians.Vals, dupGuardians.Vals[0])
+		dupGuardians.Total = 2
+
+		err := cw_wormhole.SubmitGuardianSetUpdate(t, ctx, wormchain, contractAddr, dupGuardians, uint32(initialIndex+1), signingGuardians)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "GuardianSignatureError")
+	})
+
+	t.Run("wrong signing guardian set", func(t *testing.T) {
+		// Create new guardians and try to use them to sign the update
+		wrongSigners := guardians.CreateValSet(t, numVals)
+		newGuardians := guardians.CreateValSet(t, numVals+1)
+
+		err := cw_wormhole.SubmitGuardianSetUpdate(t, ctx, wormchain, contractAddr, newGuardians, uint32(initialIndex+1), wrongSigners)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "governance VAAs must be signed by the current guardian set")
+	})
+
+	t.Run("insufficient signatures", func(t *testing.T) {
+		// Create a guardian set with only one signer (below quorum)
+		insufficientSigners := guardians.CreateValSet(t, 1)
+		newGuardians := guardians.CreateValSet(t, numVals+1)
+
+		err := cw_wormhole.SubmitGuardianSetUpdate(t, ctx, wormchain, contractAddr, newGuardians, uint32(initialIndex+1), insufficientSigners)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "governance VAAs must be signed by the current guardian set")
+
+		// too many signatures
+		insufficientSigners = guardians.CreateValSet(t, numVals+2)
+		err = cw_wormhole.SubmitGuardianSetUpdate(t, ctx, wormchain, contractAddr, newGuardians, uint32(initialIndex+1), insufficientSigners)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "GuardianSignatureError")
+	})
+
+	// Verify signing validators did not change
+	cw_wormhole.VerifyGuardianSet(t, ctx, wormchain, contractAddr, signingGuardians, initialIndex+1)
+}
+
+// TestCWWormholeContractUpgrade tests the SubmitContractUpgrade function of the cw_wormhole contract
+func TestCWWormholeContractUpgrade(t *testing.T) {
+	// Setup chain and contract
+	numVals := 1
+	guardians := guardians.CreateValSet(t, numVals)
+	chains := CreateChains(t, "v2.24.2", *guardians)
+	ctx, _, _, _ := BuildInterchain(t, chains)
+
+	// Chains
+	wormchain := chains[0].(*cosmos.CosmosChain)
+
+	// Deploy contract to wormhole
+	coreInstantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, vaa.ChainIDWormchain, guardians)
+	contractInfo := helpers.StoreAndInstantiateWormholeContract(t, ctx, wormchain, "faucet", "./contracts/cw_wormhole.wasm", "wormhole_core", coreInstantiateMsg, guardians)
+	wormContractAddr := contractInfo.Address
+
+	// Store a new version of the contract to upgrade to
+	wormNewCodeId := helpers.StoreContract(t, ctx, wormchain, "faucet", "./contracts/cw_wormhole.wasm", guardians)
+
+	t.Run("successful upgrade", func(t *testing.T) {
+		err := cw_wormhole.SubmitContractUpgrade(t, ctx, guardians, wormchain, wormContractAddr, wormNewCodeId)
+		require.NoError(t, err)
+
+		contractInfo = helpers.QueryContractInfo(t, wormchain, ctx, wormContractAddr)
+		require.NoError(t, err)
+		require.Equal(t, wormNewCodeId, contractInfo.ContractInfo.CodeID)
+	})
+
+	t.Run("invalid code id", func(t *testing.T) {
+		err := cw_wormhole.SubmitContractUpgrade(t, ctx, guardians, wormchain, wormContractAddr, "999999")
+		require.Error(t, err)
+	})
+
+	// VAA payload to upgrade contract is not allowed on Wormchain, must use the wormhole module
+	t.Run("upgrade wormhole contract with vaa: fails - use x/wormhole", func(t *testing.T) {
+		// Submit VAA
+		err := cw_wormhole.SubmitContractUpgradeWithVaa(t, ctx, guardians, 0, vaa.ChainIDWormchain, wormchain, wormContractAddr, wormNewCodeId, "faucet")
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "must use x/wormhole")
+	})
+
+	// Test osmo chain
+	osmo := chains[2].(*cosmos.CosmosChain)
+	users := interchaintest.GetAndFundTestUsers(t, ctx, t.Name(), 10_000_000_000, osmo)
+	osmoUser := users[0]
+
+	// Deploy contract to osmo
+	osmoCodeId, err := osmo.StoreContract(ctx, osmoUser.KeyName, "./contracts/cw_wormhole.wasm")
+	require.NoError(t, err)
+
+	instantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, vaa.ChainIDOsmosis, guardians)
+
+	osmoContractAddr, err := osmo.InstantiateContract(ctx, osmoUser.KeyName, osmoCodeId, instantiateMsg, false, "--admin", osmoUser.Bech32Address("osmo"))
+	require.NoError(t, err)
+
+	// Update admin to contract addr
+	_, err = osmo.GetFullNode().ExecTx(ctx, osmoUser.KeyName, "wasm", "set-contract-admin", osmoContractAddr, osmoContractAddr)
+	require.NoError(t, err)
+	err = testutil.WaitForBlocks(ctx, 2, osmo)
+	require.NoError(t, err)
+
+	// Verify new admin is contract
+	contractInfo = helpers.QueryContractInfo(t, osmo, ctx, osmoContractAddr)
+	require.NoError(t, err)
+	require.Equal(t, osmoContractAddr, contractInfo.ContractInfo.Admin)
+
+	// Store a new version of the contract to upgrade to
+	osmoNewCodeId, err := osmo.StoreContract(ctx, osmoUser.KeyName, "./contracts/cw_wormhole.wasm")
+	require.NoError(t, err)
+
+	t.Run("migrate osmosis via payload", func(t *testing.T) {
+		// Submit VAA
+		err := cw_wormhole.SubmitContractUpgradeWithVaa(t, ctx, guardians, 0, vaa.ChainIDOsmosis, osmo, osmoContractAddr, osmoNewCodeId, osmoUser.KeyName)
+		require.NoError(t, err)
+
+		contractInfo := helpers.QueryContractInfo(t, osmo, ctx, osmoContractAddr)
+		require.NoError(t, err)
+		require.Equal(t, osmoNewCodeId, contractInfo.ContractInfo.CodeID)
+	})
+
+}
+
+// TestCWWormholeSetFee tests the SetFee function of the cw_wormhole contract
+func TestCWWormholeSetFee(t *testing.T) {
+	// Setup chain and contract
+	numVals := 1
+	guardians := guardians.CreateValSet(t, numVals)
+	chain := createSingleNodeCluster(t, "v2.24.2", *guardians)
+	ctx, _ := buildSingleNodeInterchain(t, chain)
+	wormchain := chain.(*cosmos.CosmosChain)
+
+	// Deploy contract
+	coreInstantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, vaa.ChainIDWormchain, guardians)
+	contractInfo := helpers.StoreAndInstantiateWormholeContract(t, ctx, wormchain, "faucet", "./contracts/cw_wormhole.wasm", "wormhole_core", coreInstantiateMsg, guardians)
+	contractAddr := contractInfo.Address
+
+	// wrapper around helper function for cleaner test code
+	submitFeeUpdate := func(amount string, replay bool) (*cw_wormhole.TxResponse, error) {
+		return cw_wormhole.SubmitFeeUpdate(t, ctx, guardians, wormchain, contractAddr, amount, replay)
+	}
+
+	t.Run("successful fee update", func(t *testing.T) {
+		txResponse, err := submitFeeUpdate("1000000", true) // Set fee to 1 WORM (1000000 uworm)
+		require.NoError(t, err)
+		cw_wormhole.VerifyEventAttributes(t, txResponse, map[string]string{
+			"action":         "fee_change",
+			"new_fee.amount": "1000000",
+			"new_fee.denom":  "uworm",
+		})
+		cw_wormhole.VerifyFee(t, ctx, wormchain, contractAddr, "1000000")
+
+		// post a message with fee under the new fee
+		err = cw_wormhole.PostMessageWithFee(t, ctx, wormchain, contractAddr, "message1", 999999)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "FeeTooLow")
+
+		// post a message with fee equal to the new fee
+		err = cw_wormhole.PostMessageWithFee(t, ctx, wormchain, contractAddr, "message2", 1000000)
+		require.NoError(t, err)
+
+		// post a message with fee above the new fee
+		err = cw_wormhole.PostMessageWithFee(t, ctx, wormchain, contractAddr, "message3", 1000001)
+		require.NoError(t, err)
+	})
+
+	t.Run("zero fee", func(t *testing.T) {
+		txResponse, err := submitFeeUpdate("0", false)
+		require.NoError(t, err)
+		cw_wormhole.VerifyEventAttributes(t, txResponse, map[string]string{
+			"action":         "fee_change",
+			"new_fee.amount": "0",
+			"new_fee.denom":  "uworm",
+		})
+		cw_wormhole.VerifyFee(t, ctx, wormchain, contractAddr, "0")
+	})
+
+	t.Run("very large fee", func(t *testing.T) {
+		txResponse, err := submitFeeUpdate("1000000000000", false) // 1M WORM
+		require.NoError(t, err)
+		cw_wormhole.VerifyEventAttributes(t, txResponse, map[string]string{
+			"action":         "fee_change",
+			"new_fee.amount": "1000000000000",
+			"new_fee.denom":  "uworm",
+		})
+		cw_wormhole.VerifyFee(t, ctx, wormchain, contractAddr, "1000000000000")
+	})
+}
+
+// TestCWWormholeTransferFees tests transferring the accumulated fees to the core contract
+func TestCWWormholeTransferFees(t *testing.T) {
+	// Setup chain and contract
+	numVals := 1
+	guardians := guardians.CreateValSet(t, numVals)
+	chain := createSingleNodeCluster(t, "v2.24.2", *guardians)
+	ctx, _ := buildSingleNodeInterchain(t, chain)
+	wormchain := chain.(*cosmos.CosmosChain)
+
+	// Deploy contract
+	coreInstantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, vaa.ChainIDWormchain, guardians)
+	contractInfo := helpers.StoreAndInstantiateWormholeContract(t, ctx, wormchain, "faucet", "./contracts/cw_wormhole.wasm", "wormhole_core", coreInstantiateMsg, guardians)
+	contractAddr := contractInfo.Address
+
+	users := interchaintest.GetAndFundTestUsers(t, ctx, t.Name(), 1, wormchain)
+	user := users[0]
+	userAddr := user.Bech32Address("wormhole")
+
+	t.Run("successful fee transfer", func(t *testing.T) {
+		// Set fee to 1000000 uworm
+		_, err := cw_wormhole.SubmitFeeUpdate(t, ctx, guardians, wormchain, contractAddr, "1000000", false)
+		require.NoError(t, err)
+
+		// Post some messages with fees to build up balance
+		err = cw_wormhole.PostMessageWithFee(t, ctx, wormchain, contractAddr, "message1", 1000000)
+		require.NoError(t, err)
+		err = cw_wormhole.PostMessageWithFee(t, ctx, wormchain, contractAddr, "message2", 1000000)
+		require.NoError(t, err)
+
+		// Get recipient's initial balance
+		initialBalance, err := cw_wormhole.GetUwormBalance(t, ctx, wormchain, userAddr)
+		require.NoError(t, err)
+
+		// Transfer 1500000 uworm
+		_, err = cw_wormhole.SubmitTransferFee(t, ctx, guardians, wormchain, contractAddr, []byte(user.Address), "1500000", true)
+		require.NoError(t, err)
+
+		// Verify successful transfer
+		finalBalance, err := cw_wormhole.GetUwormBalance(t, ctx, wormchain, userAddr)
+		require.NoError(t, err)
+		require.Equal(t, initialBalance+1500000, finalBalance)
+	})
+
+	t.Run("transfer more than balance", func(t *testing.T) {
+		_, err := cw_wormhole.SubmitTransferFee(t, ctx, guardians, wormchain, contractAddr, []byte(user.Address), "10000000000", false)
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "insufficient funds")
+	})
+
+	t.Run("invalid recipient", func(t *testing.T) {
+		_, err := cw_wormhole.SubmitTransferFee(t, ctx, guardians, wormchain, contractAddr, []byte("invalid"), "1000000", false)
+		require.Error(t, err)
+	})
+
+	t.Run("zero amount - invalid coins", func(t *testing.T) {
+		_, err := cw_wormhole.SubmitTransferFee(t, ctx, guardians, wormchain, contractAddr, []byte(user.Address), "0", false)
+		require.Error(t, err)
+	})
+}

+ 110 - 0
wormchain/interchaintest/helpers/cw_wormhole/cw_wormhole.go

@@ -0,0 +1,110 @@
+/* Code generated by github.com/srdtrk/go-codegen, DO NOT EDIT. */
+package cw_wormhole
+
+// The instantiation parameters of the core bridge contract. See [`crate::state::ConfigInfo`] for more details on what these fields mean.
+type InstantiateMsg struct {
+	FeeDenom            string `json:"fee_denom"`
+	GovAddress          Binary `json:"gov_address"`
+	GovChain            int    `json:"gov_chain"`
+	GuardianSetExpirity int    `json:"guardian_set_expirity"`
+	// Guardian set to initialise the contract with.
+	InitialGuardianSet GuardianSetInfo `json:"initial_guardian_set"`
+	ChainId            int             `json:"chain_id"`
+}
+
+type ExecuteMsg struct {
+	SubmitVaa   *ExecuteMsg_SubmitVAA   `json:"submit_v_a_a,omitempty"`
+	PostMessage *ExecuteMsg_PostMessage `json:"post_message,omitempty"`
+}
+
+type QueryMsg struct {
+	GuardianSetInfo *QueryMsg_GuardianSetInfo `json:"guardian_set_info,omitempty"`
+	VerifyVaa       *QueryMsg_VerifyVAA       `json:"verify_v_a_a,omitempty"`
+	GetState        *QueryMsg_GetState        `json:"get_state,omitempty"`
+	QueryAddressHex *QueryMsg_QueryAddressHex `json:"query_address_hex,omitempty"`
+}
+
+type ExecuteMsg_PostMessage struct {
+	Message Binary `json:"message"`
+	Nonce   int    `json:"nonce"`
+}
+
+type GuardianSetInfoResponse struct {
+	Addresses        []GuardianAddress `json:"addresses"`
+	GuardianSetIndex int               `json:"guardian_set_index"`
+}
+
+/*
+A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.
+
+# Examples
+
+Use `from` to create instances of this and `u128` to get the value out:
+
+``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);
+
+let b = Uint128::from(42u64); assert_eq!(b.u128(), 42);
+
+let c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```
+*/
+type Uint128 string
+
+type GetStateResponse struct {
+	Fee Coin `json:"fee"`
+}
+
+type GuardianAddress struct {
+	Bytes Binary `json:"bytes"`
+}
+
+type QueryMsg_GetState struct{}
+
+type QueryMsg_QueryAddressHex struct {
+	Address string `json:"address"`
+}
+
+/*
+Binary is a wrapper around Vec<u8> to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.
+
+This is only needed as serde-json-{core,wasm} has a horrible encoding for Vec<u8>. See also <https://github.com/CosmWasm/cosmwasm/blob/main/docs/MESSAGE_TYPES.md>.
+*/
+type Binary string
+
+type ExecuteMsg_SubmitVAA struct {
+	Vaa Binary `json:"vaa"`
+}
+
+type QueryMsg_VerifyVAA struct {
+	BlockTime int    `json:"block_time"`
+	Vaa       Binary `json:"vaa"`
+}
+
+type ParsedVAA struct {
+	ConsistencyLevel int    `json:"consistency_level"`
+	EmitterAddress   []int  `json:"emitter_address"`
+	Hash             []int  `json:"hash"`
+	LenSigners       int    `json:"len_signers"`
+	Timestamp        int    `json:"timestamp"`
+	Version          int    `json:"version"`
+	EmitterChain     int    `json:"emitter_chain"`
+	GuardianSetIndex int    `json:"guardian_set_index"`
+	Nonce            int    `json:"nonce"`
+	Payload          []byte `json:"payload"`
+	Sequence         int    `json:"sequence"`
+}
+
+type Coin struct {
+	Amount Uint128 `json:"amount"`
+	Denom  string  `json:"denom"`
+}
+
+type GuardianSetInfo struct {
+	Addresses      []GuardianAddress `json:"addresses"`
+	ExpirationTime int               `json:"expiration_time"`
+}
+
+type GetAddressHexResponse struct {
+	Hex string `json:"hex"`
+}
+
+type QueryMsg_GuardianSetInfo struct{}

+ 408 - 0
wormchain/interchaintest/helpers/cw_wormhole/helpers.go

@@ -0,0 +1,408 @@
+package cw_wormhole
+
+import (
+	"bytes"
+	"context"
+	"encoding/base64"
+	"encoding/binary"
+	"encoding/json"
+	"math/big"
+	"testing"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/strangelove-ventures/interchaintest/v4/chain/cosmos"
+	"github.com/strangelove-ventures/interchaintest/v4/testutil"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/wormhole-foundation/wormchain/interchaintest/guardians"
+	"github.com/wormhole-foundation/wormchain/interchaintest/helpers"
+	"github.com/wormhole-foundation/wormhole/sdk/vaa"
+)
+
+type GuardianSetQueryResponse struct {
+	Data GuardianSetInfoResponse `json:"data"`
+}
+
+type VerifyVAAQueryResponse struct {
+	Data ParsedVAA `json:"data"`
+}
+
+type GetStateQueryResponse struct {
+	Data GetStateResponse `json:"data"`
+}
+
+type QueryAddressHexQueryResponse struct {
+	Data GetAddressHexResponse `json:"data"`
+}
+
+// Custom response type to handle string numbers
+type TxResponse struct {
+	Code uint32              `json:"code"`
+	Logs sdk.ABCIMessageLogs `json:"logs"`
+}
+
+// SubmitGuardianSetUpdate submits a VAA to update the guardian set
+func SubmitGuardianSetUpdate(
+	t *testing.T,
+	ctx context.Context,
+	wormchain *cosmos.CosmosChain,
+	contractAddr string,
+	newGuardians *guardians.ValSet,
+	newIndex uint32,
+	signingGuardians *guardians.ValSet,
+) error {
+	// Create guardian set update payload
+	guardianKeys := make([]common.Address, len(newGuardians.Vals))
+	for i, g := range newGuardians.Vals {
+		copy(guardianKeys[i][:], g.Addr)
+	}
+
+	updateMsg := vaa.BodyGuardianSetUpdate{
+		Keys:     guardianKeys,
+		NewIndex: newIndex,
+	}
+
+	payload, err := updateMsg.Serialize()
+	require.NoError(t, err)
+
+	// Generate and sign the governance VAA using the signing guardian set
+	guardianSetIndex := helpers.QueryConsensusGuardianSetIndex(t, wormchain, ctx)
+	govVaa := helpers.GenerateGovernanceVaa(uint32(guardianSetIndex), signingGuardians, payload)
+	vaaBz, err := govVaa.Marshal()
+	require.NoError(t, err)
+
+	encodedVaa := base64.StdEncoding.EncodeToString(vaaBz)
+	executeVAAPayload, err := json.Marshal(ExecuteMsg{
+		SubmitVaa: &ExecuteMsg_SubmitVAA{
+			Vaa: Binary(encodedVaa),
+		},
+	})
+	require.NoError(t, err)
+
+	// Submit VAA
+	_, err = wormchain.ExecuteContract(ctx, "faucet", contractAddr, string(executeVAAPayload))
+	if err != nil {
+		return err
+	}
+
+	// Wait for transaction
+	return testutil.WaitForBlocks(ctx, 2, wormchain)
+}
+
+// VerifyGuardianSet verifies the guardian set in the contract state
+func VerifyGuardianSet(
+	t *testing.T,
+	ctx context.Context,
+	wormchain *cosmos.CosmosChain,
+	contractAddr string,
+	expectedGuardians *guardians.ValSet,
+	expectedIndex int,
+) {
+	var guardianSetResp GuardianSetQueryResponse
+	err := wormchain.QueryContract(ctx, contractAddr, QueryMsg{
+		GuardianSetInfo: &QueryMsg_GuardianSetInfo{},
+	}, &guardianSetResp)
+	require.NoError(t, err)
+
+	require.Equal(t, len(expectedGuardians.Vals), len(guardianSetResp.Data.Addresses), "unexpected number of guardians")
+	require.Equal(t, expectedIndex, guardianSetResp.Data.GuardianSetIndex, "unexpected guardian set index")
+
+	for i, val := range expectedGuardians.Vals {
+		found := false
+		for _, guardian := range guardianSetResp.Data.Addresses {
+			decoded, err := base64.StdEncoding.DecodeString(string(guardian.Bytes))
+			require.NoError(t, err)
+			guardianDecodedBytes := []byte(decoded)
+			if bytes.Equal(val.Addr, guardianDecodedBytes) {
+				found = true
+				break
+			}
+		}
+		require.True(t, found, "guardian %d not found in guardian set", i)
+	}
+}
+
+// SubmitContractUpgrade submits a VAA to upgrade the contract code
+func SubmitContractUpgrade(
+	t *testing.T,
+	ctx context.Context,
+	guardians *guardians.ValSet,
+	wormchain *cosmos.CosmosChain,
+	contractAddr string,
+	newCodeId string,
+) error {
+	if err := helpers.MigrateContract(t, ctx, wormchain, "faucet", contractAddr, newCodeId, "{}", guardians); err != nil {
+		return err
+	}
+
+	// Wait for transaction
+	return testutil.WaitForBlocks(ctx, 2, wormchain)
+}
+
+func SubmitContractUpgradeWithVaa(
+	t *testing.T,
+	ctx context.Context,
+	guardians *guardians.ValSet,
+	guardianSetIndex uint64,
+	vaaChainId vaa.ChainID,
+	chain *cosmos.CosmosChain,
+	contractAddr string,
+	newCodeId string,
+	keyName string,
+) error {
+	// convert newCodeId to Uint256
+	var newCodeIdBz [32]byte
+	newCodeIdInt := new(big.Int)
+	newCodeIdInt.SetString(newCodeId, 10)
+	newCodeIdInt.FillBytes(newCodeIdBz[:])
+
+	// Create contract upgrade payload
+	updateMsg := vaa.BodyContractUpgrade{
+		ChainID:     vaaChainId,
+		NewContract: vaa.Address(newCodeIdBz),
+	}
+
+	payload, err := updateMsg.Serialize()
+	require.NoError(t, err)
+
+	// Generate and sign the governance VAA
+	govVaa := helpers.GenerateGovernanceVaa(uint32(guardianSetIndex), guardians, payload)
+	vaaBz, err := govVaa.Marshal()
+	require.NoError(t, err)
+
+	encodedVaa := base64.StdEncoding.EncodeToString(vaaBz)
+	executeVAAPayload, err := json.Marshal(ExecuteMsg{
+		SubmitVaa: &ExecuteMsg_SubmitVAA{
+			Vaa: Binary(encodedVaa),
+		},
+	})
+	require.NoError(t, err)
+
+	// Submit VAA
+	_, err = chain.ExecuteContract(ctx, keyName, contractAddr, string(executeVAAPayload))
+	return err
+}
+
+// SubmitFeeUpdate submits a VAA to update the fee amount
+func SubmitFeeUpdate(
+	t *testing.T,
+	ctx context.Context,
+	guardians *guardians.ValSet,
+	wormchain *cosmos.CosmosChain,
+	contractAddr string,
+	amount string,
+	replay bool,
+) (*TxResponse, error) {
+	// Get current guardian set index
+	guardianSetIndex := helpers.QueryConsensusGuardianSetIndex(t, wormchain, ctx)
+
+	// Create a fixed 32-byte array for the fee amount
+	var amountBytes [32]byte
+	amountInt := new(big.Int)
+	amountInt.SetString(amount, 10)
+	amountInt.FillBytes(amountBytes[:])
+
+	// Create governance VAA payload
+	// [32 bytes] Core Module
+	// [1 byte]  Action (3 for set fee)
+	// [2 bytes] ChainID (0 for universal)
+	// [32 bytes] Amount
+	buf := new(bytes.Buffer)
+	buf.Write(vaa.CoreModule)
+	vaa.MustWrite(buf, binary.BigEndian, vaa.ActionCoreSetMessageFee)
+	vaa.MustWrite(buf, binary.BigEndian, uint16(0)) // ChainID 0 for universal
+	buf.Write(amountBytes[:])
+
+	// Generate and sign governance VAA
+	govVaa := helpers.GenerateGovernanceVaa(uint32(guardianSetIndex), guardians, buf.Bytes())
+	vaaBz, err := govVaa.Marshal()
+	require.NoError(t, err)
+
+	// Rest of the function remains the same...
+	encodedVaa := base64.StdEncoding.EncodeToString(vaaBz)
+	executeVAAPayload, err := json.Marshal(ExecuteMsg{
+		SubmitVaa: &ExecuteMsg_SubmitVAA{
+			Vaa: Binary(encodedVaa),
+		},
+	})
+	require.NoError(t, err)
+
+	// Submit VAA
+	txHash, err := wormchain.ExecuteContract(ctx, "faucet", contractAddr, string(executeVAAPayload))
+	if err != nil {
+		return nil, err
+	}
+
+	// Wait for transaction
+	err = testutil.WaitForBlocks(ctx, 2, wormchain)
+	require.NoError(t, err)
+
+	// Replay the transaction
+	if replay {
+		_, err = wormchain.ExecuteContract(ctx, "faucet", contractAddr, string(executeVAAPayload))
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "VaaAlreadyExecuted")
+	}
+
+	// Query transaction result
+	txResult, _, err := wormchain.Validators[0].ExecQuery(ctx, "tx", txHash)
+	require.NoError(t, err)
+
+	// Parse response
+	var txResponse TxResponse
+	err = json.Unmarshal(txResult, &txResponse)
+	require.NoError(t, err)
+
+	return &txResponse, nil
+}
+
+// VerifyFee verifies the fee amount in the contract state
+func VerifyFee(
+	t *testing.T,
+	ctx context.Context,
+	wormchain *cosmos.CosmosChain,
+	contractAddr string,
+	expectedAmount string,
+) {
+	var stateResp GetStateQueryResponse
+	err := wormchain.QueryContract(ctx, contractAddr, QueryMsg{
+		GetState: &QueryMsg_GetState{},
+	}, &stateResp)
+	require.NoError(t, err)
+	require.Equal(t, "uworm", stateResp.Data.Fee.Denom)
+	require.Equal(t, Uint128(expectedAmount), stateResp.Data.Fee.Amount)
+}
+
+// VerifyEventAttributes verifies the attributes in a wasm tx response
+func VerifyEventAttributes(t *testing.T, txResponse *TxResponse, expectedAttributes map[string]string) {
+	require.Equal(t, uint32(0), txResponse.Code, "tx should succeed")
+
+	// Find the wasm event
+	var wasmEvent *sdk.StringEvent
+	for _, log := range txResponse.Logs {
+		for _, event := range log.Events {
+			if event.Type == "wasm" {
+				wasmEvent = &event
+				break
+			}
+		}
+	}
+	require.NotNil(t, wasmEvent, "wasm event not found")
+
+	// Helper to find attribute value
+	findAttribute := func(key string) string {
+		for _, attr := range wasmEvent.Attributes {
+			if attr.Key == key {
+				return attr.Value
+			}
+		}
+		return ""
+	}
+
+	// Verify each expected attribute
+	for key, expectedValue := range expectedAttributes {
+		actualValue := findAttribute(key)
+		require.Equal(t, expectedValue, actualValue,
+			"unexpected value for attribute %s", key)
+	}
+}
+
+// PostMessageWithFee posts a message to the contract with a fee
+func PostMessageWithFee(
+	t *testing.T,
+	ctx context.Context,
+	wormchain *cosmos.CosmosChain,
+	contractAddr string,
+	message string,
+	fee int64,
+) error {
+	messageBase64 := base64.StdEncoding.EncodeToString([]byte(message))
+	executeMsg, err := json.Marshal(ExecuteMsg{
+		PostMessage: &ExecuteMsg_PostMessage{
+			Message: Binary(messageBase64),
+			Nonce:   1,
+		},
+	})
+	require.NoError(t, err)
+
+	funds := sdk.Coins{sdk.NewInt64Coin("uworm", fee)}
+	_, err = wormchain.ExecuteContractWithAmount(ctx, "faucet", contractAddr, string(executeMsg), funds)
+	return err
+}
+
+// SubmitTransferFee submits a VAA to transfer fees to an address
+func SubmitTransferFee(
+	t *testing.T,
+	ctx context.Context,
+	guardians *guardians.ValSet,
+	wormchain *cosmos.CosmosChain,
+	contractAddr string,
+	addrBytes []byte,
+	amount string,
+	replay bool,
+) (*TxResponse, error) {
+	// Created a fixed 32-byte array for the recipient address
+	var recipientBytes [32]byte
+	copy(recipientBytes[32-len(addrBytes):], addrBytes)
+
+	// Create a fixed 32-byte array for the fee amount
+	var amountBytes [32]byte
+	amountInt := new(big.Int)
+	amountInt.SetString(amount, 10)
+	amountInt.FillBytes(amountBytes[:])
+
+	buf := new(bytes.Buffer)
+	buf.Write(vaa.CoreModule)
+	vaa.MustWrite(buf, binary.BigEndian, vaa.ActionCoreTransferFees)
+	vaa.MustWrite(buf, binary.BigEndian, uint16(0))
+	buf.Write(recipientBytes[:])
+	buf.Write(amountBytes[:])
+
+	guardianSetIndex := helpers.QueryConsensusGuardianSetIndex(t, wormchain, ctx)
+	govVaa := helpers.GenerateGovernanceVaa(uint32(guardianSetIndex), guardians, buf.Bytes())
+	vaaBz, err := govVaa.Marshal()
+	require.NoError(t, err)
+
+	encodedVaa := base64.StdEncoding.EncodeToString(vaaBz)
+	executeVAAPayload, err := json.Marshal(ExecuteMsg{
+		SubmitVaa: &ExecuteMsg_SubmitVAA{
+			Vaa: Binary(encodedVaa),
+		},
+	})
+	require.NoError(t, err)
+
+	txHash, err := wormchain.ExecuteContract(ctx, "faucet", contractAddr, string(executeVAAPayload))
+	if err != nil {
+		return nil, err
+	}
+
+	err = testutil.WaitForBlocks(ctx, 2, wormchain)
+	require.NoError(t, err)
+
+	if replay {
+		_, err = wormchain.ExecuteContract(ctx, "faucet", contractAddr, string(executeVAAPayload))
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "VaaAlreadyExecuted")
+	}
+
+	txResult, _, err := wormchain.Validators[0].ExecQuery(ctx, "tx", txHash)
+	require.NoError(t, err)
+
+	var txResponse TxResponse
+	err = json.Unmarshal(txResult, &txResponse)
+	require.NoError(t, err)
+
+	return &txResponse, nil
+}
+
+// GetUwormBalance returns the balance of uworm tokens for an address
+func GetUwormBalance(t *testing.T, ctx context.Context, wormchain *cosmos.CosmosChain, addr string) (int64, error) {
+	coins, err := wormchain.GetBalance(ctx, addr, "uworm")
+	if err != nil {
+		return 0, err
+	}
+
+	return coins, nil
+}

+ 2 - 2
wormchain/interchaintest/helpers/migrate_contract.go

@@ -35,7 +35,7 @@ func MigrateContract(
 	codeId string,
 	message string,
 	guardians *guardians.ValSet,
-) {
+) error {
 
 	node := chain.GetFullNode()
 
@@ -48,5 +48,5 @@ func MigrateContract(
 	vHex := hex.EncodeToString(vBz)
 
 	_, err = node.ExecTx(ctx, keyName, "wormhole", "migrate", contractAddr, codeId, message, vHex, "--gas", "auto")
-	require.NoError(t, err)
+	return err
 }

+ 23 - 0
wormchain/interchaintest/helpers/vaa.go

@@ -1,8 +1,11 @@
 package helpers
 
 import (
+	"encoding/hex"
+	"testing"
 	"time"
 
+	"github.com/stretchr/testify/require"
 	"github.com/wormhole-foundation/wormhole/sdk/vaa"
 
 	"github.com/wormhole-foundation/wormchain/interchaintest/guardians"
@@ -44,3 +47,23 @@ func GenerateGovernanceVaa(index uint32,
 	latestSequence = latestSequence + 1
 	return signVaa(*v, signers)
 }
+
+func GenerateEmptyVAA(
+	t *testing.T,
+	guardians *guardians.ValSet,
+	moduleStr string,
+	action vaa.GovernanceAction,
+	chainID vaa.ChainID,
+) string {
+
+	payloadBz, err := vaa.EmptyPayloadVaa(moduleStr, action, chainID)
+	require.NoError(t, err)
+	v := generateVaa(0, guardians, vaa.GovernanceChain, vaa.GovernanceEmitter, payloadBz)
+
+	v = signVaa(v, guardians)
+	vBz, err := v.Marshal()
+	require.NoError(t, err)
+	vHex := hex.EncodeToString(vBz)
+
+	return vHex
+}

+ 24 - 2
wormchain/interchaintest/helpers/wormhole_core.go

@@ -1,9 +1,11 @@
 package helpers
 
 import (
+	"context"
 	"encoding/json"
 	"testing"
 
+	"github.com/strangelove-ventures/interchaintest/v4/chain/cosmos"
 	"github.com/strangelove-ventures/interchaintest/v4/ibc"
 	"github.com/stretchr/testify/require"
 	"github.com/wormhole-foundation/wormchain/interchaintest/guardians"
@@ -28,7 +30,7 @@ type GuardianAddress struct {
 	Bytes []byte `json:"bytes"`
 }
 
-func CoreContractInstantiateMsg(t *testing.T, cfg ibc.ChainConfig, guardians *guardians.ValSet) string {
+func CoreContractInstantiateMsg(t *testing.T, cfg ibc.ChainConfig, vaaChainId vaa.ChainID, guardians *guardians.ValSet) string {
 	guardianAddresses := []GuardianAddress{}
 	for i := 0; i < guardians.Total; i++ {
 		guardianAddresses = append(guardianAddresses, GuardianAddress{
@@ -44,7 +46,7 @@ func CoreContractInstantiateMsg(t *testing.T, cfg ibc.ChainConfig, guardians *gu
 			ExpirationTime: 0,
 		},
 		GuardianSetExpirity: 86400,
-		ChainId:             uint16(vaa.ChainIDWormchain),
+		ChainId:             uint16(vaaChainId),
 		FeeDenom:            cfg.Denom,
 	}
 	msgBz, err := json.Marshal(msg)
@@ -52,3 +54,23 @@ func CoreContractInstantiateMsg(t *testing.T, cfg ibc.ChainConfig, guardians *gu
 
 	return string(msgBz)
 }
+
+// QueryConsensusGuardianSetIndex queries the index of the consensus guardian set
+func QueryConsensusGuardianSetIndex(t *testing.T, wormchain *cosmos.CosmosChain, ctx context.Context) uint64 {
+	stdout, _, err := wormchain.GetFullNode().ExecQuery(ctx,
+		"wormhole", "show-consensus-guardian-set-index",
+	)
+	require.NoError(t, err)
+
+	res := new(ConsensusGuardianSetIndexResponse)
+	err = json.Unmarshal(stdout, res)
+	require.NoError(t, err)
+
+	return res.ConsensusGuardianSetIndex.Index
+}
+
+type ConsensusGuardianSetIndexResponse struct {
+	ConsensusGuardianSetIndex struct {
+		Index uint64 `json:"index"`
+	} `json:"ConsensusGuardianSetIndex"`
+}

+ 3 - 3
wormchain/interchaintest/ibc_receiver_test.go

@@ -34,7 +34,7 @@ func createChains(t *testing.T, wormchainVersion string, guardians guardians.Val
 	wormchainConfig.Images[0].Version = wormchainVersion
 
 	// Create chain factory with wormchain
-	wormchainConfig.ModifyGenesis = ModifyGenesis(votingPeriod, maxDepositPeriod, guardians)
+	wormchainConfig.ModifyGenesis = ModifyGenesis(votingPeriod, maxDepositPeriod, guardians, false)
 
 	cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{
 		{
@@ -338,7 +338,7 @@ func instantiateWormholeIbcContracts(t *testing.T, ctx context.Context,
 	guardians *guardians.ValSet) (helpers.ContractInfoResponse, helpers.ContractInfoResponse) {
 
 	// Instantiate the Wormchain core contract
-	coreInstantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, guardians)
+	coreInstantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, vaa.ChainIDWormchain, guardians)
 	wormchainCoreContractInfo := helpers.StoreAndInstantiateWormholeContract(t, ctx, wormchain, "faucet", "./contracts/wormhole_core.wasm", "wormhole_core", coreInstantiateMsg, guardians)
 
 	// Store wormhole-ibc-receiver contract on wormchain
@@ -354,7 +354,7 @@ func instantiateWormholeIbcContracts(t *testing.T, ctx context.Context,
 	require.NotEmpty(t, wormchainReceiverContractInfo.ContractInfo.IbcPortID, "wormchain (wormchain-ibc-receiver) contract port id is nil")
 
 	// Store and instantiate wormhole-ibc contract on osmosis
-	senderInstantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, guardians)
+	senderInstantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, vaa.ChainIDWormchain, guardians)
 	senderCodeId, err := remoteChain.StoreContract(ctx, "faucet", "./contracts/wormhole_ibc.wasm")
 	require.NoError(t, err)
 	senderContractAddr, err := remoteChain.InstantiateContract(ctx, "faucet", senderCodeId, senderInstantiateMsg, true)

+ 1 - 1
wormchain/interchaintest/malformed_payload_test.go

@@ -58,7 +58,7 @@ func TestMalformedPayload(t *testing.T) {
 	fmt.Println("Core contract code id: ", coreContractCodeId)
 
 	// Instantiate wormhole core contract
-	coreInstantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, guardians)
+	coreInstantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, vaa.ChainIDWormchain, guardians)
 	coreContractAddr := helpers.InstantiateContract(t, ctx, wormchain, "faucet", coreContractCodeId, "wormhole_core", coreInstantiateMsg, guardians)
 	fmt.Println("Core contract address: ", coreContractAddr)
 

+ 18 - 11
wormchain/interchaintest/setup.go

@@ -70,7 +70,7 @@ func CreateChains(t *testing.T, wormchainVersion string, guardians guardians.Val
 	wormchainConfig.Images[0].Version = wormchainVersion
 
 	// Create chain factory with wormchain
-	wormchainConfig.ModifyGenesis = ModifyGenesis(votingPeriod, maxDepositPeriod, guardians)
+	wormchainConfig.ModifyGenesis = ModifyGenesis(votingPeriod, maxDepositPeriod, guardians, false)
 
 	cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{
 		{
@@ -167,7 +167,7 @@ func BuildInterchain(t *testing.T, chains []ibc.Chain) (context.Context, ibc.Rel
 // * Set Guardian Set List using new val set
 // * Set Guardian Validator List using new val set
 // * Allow list the faucet address
-func ModifyGenesis(votingPeriod string, maxDepositPeriod string, guardians guardians.ValSet) func(ibc.ChainConfig, []byte) ([]byte, error) {
+func ModifyGenesis(votingPeriod string, maxDepositPeriod string, guardians guardians.ValSet, skipRelayers bool) func(ibc.ChainConfig, []byte) ([]byte, error) {
 	return func(chainConfig ibc.ChainConfig, genbz []byte) ([]byte, error) {
 		numVals := len(guardians.Vals)
 		g := make(map[string]interface{})
@@ -202,10 +202,13 @@ func ModifyGenesis(votingPeriod string, maxDepositPeriod string, guardians guard
 			return nil, fmt.Errorf("failed to get faucet address: %w", err)
 		}
 
-		// Get relayer address
-		relayerAddress, err := dyno.Get(g, "app_state", "auth", "accounts", numVals+1, "address")
-		if err != nil {
-			return nil, fmt.Errorf("failed to get relayer address: %w", err)
+		var relayerAddress interface{}
+		if !skipRelayers {
+			// Get relayer address
+			relayerAddress, err = dyno.Get(g, "app_state", "auth", "accounts", numVals+1, "address")
+			if err != nil {
+				return nil, fmt.Errorf("failed to get relayer address: %w", err)
+			}
 		}
 
 		// Set guardian set list and validators
@@ -236,11 +239,15 @@ func ModifyGenesis(votingPeriod string, maxDepositPeriod string, guardians guard
 			AllowedAddress:   faucetAddress.(string),
 			Name:             "Faucet",
 		})
-		allowedAddresses = append(allowedAddresses, ValidatorAllowedAddress{
-			ValidatorAddress: sdk.MustBech32ifyAddressBytes(chainConfig.Bech32Prefix, validators[0]),
-			AllowedAddress:   relayerAddress.(string),
-			Name:             "Relayer",
-		})
+
+		if !skipRelayers {
+			allowedAddresses = append(allowedAddresses, ValidatorAllowedAddress{
+				ValidatorAddress: sdk.MustBech32ifyAddressBytes(chainConfig.Bech32Prefix, validators[0]),
+				AllowedAddress:   relayerAddress.(string),
+				Name:             "Relayer",
+			})
+		}
+
 		if err := dyno.Set(g, allowedAddresses, "app_state", "wormhole", "allowedAddresses"); err != nil {
 			return nil, fmt.Errorf("failed to set guardian validator list: %w", err)
 		}

+ 1 - 1
wormchain/interchaintest/upgrade_test.go

@@ -186,7 +186,7 @@ func TestUpgrade(t *testing.T) {
 	fmt.Println("Core contract code id: ", coreContractCodeId)
 
 	// Instantiate wormhole core contract
-	coreInstantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, guardians)
+	coreInstantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, vaa.ChainIDWormchain, guardians)
 	coreContractAddr := helpers.InstantiateContract(t, ctx, wormchain, "faucet", coreContractCodeId, "wormhole_core", coreInstantiateMsg, guardians)
 	fmt.Println("Core contract address: ", coreContractAddr)
 

+ 1 - 1
wormchain/interchaintest/wormchain_test.go

@@ -104,7 +104,7 @@ func TestWormchain(t *testing.T) {
 	fmt.Println("Core contract code id: ", coreContractCodeId)
 
 	// Instantiate wormhole core contract
-	coreInstantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, guardians)
+	coreInstantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, vaa.ChainIDWormchain, guardians)
 	coreContractAddr := helpers.InstantiateContract(t, ctx, wormchain, "faucet", coreContractCodeId, "wormhole_core", coreInstantiateMsg, guardians)
 	fmt.Println("Core contract address: ", coreContractAddr)