Skip to content
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The `zetacored` binary must be upgraded to trigger chain parameters data migrati
* [4437](https://github.com/zeta-chain/node/pull/4437) - have zetaclient resolve IP address from public DNS and then use only IP address in go-tss
* [4443](https://github.com/zeta-chain/node/pull/4443) - fix effective gas price calculation for zevm rpc
* [4471](https://github.com/zeta-chain/node/pull/4471) - accept uppercase receiver address in Bitcoin withdrawals
* [4502](https://github.com/zeta-chain/node/pull/4502) - update to filter by VM for evm chains requiring TSS funds migration.
* [4498](https://github.com/zeta-chain/node/pull/4498) - reindex txlogs at the rpc layer if duplicate indexes are present
* [4490](https://github.com/zeta-chain/node/pull/4490) - allow object for `tracerConfig` in the zevm debug APIs
* [4511](https://github.com/zeta-chain/node/pull/4511) - false mempool congested warning
Expand Down
15 changes: 8 additions & 7 deletions x/crosschain/keeper/msg_server_update_tss.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ func (k msgServer) UpdateTssAddress(

tssMigrators := k.zetaObserverKeeper.GetAllTssFundMigrators(ctx)

// Each connected chain should have its own tss migrator
if len(k.GetChainsSupportingTSSMigration(ctx)) != len(tssMigrators) {
// Each connected chain that needs funds migration should have its own tss migrator
// Solana, Sui, Ton do not need funds migration; updating TSS address is enough
Comment thread
kingpinXD marked this conversation as resolved.
if len(k.TSSFundsMigrationChains(ctx)) != len(tssMigrators) {
return nil, errorsmod.Wrap(
types.ErrUnableToUpdateTss,
"cannot update tss address incorrect number of migrations have been created and completed",
Expand All @@ -49,7 +50,7 @@ func (k msgServer) UpdateTssAddress(

// GetAllTssFundMigrators would return the migrators created for the current migration
// if any of the migrations is still pending we should not allow the tss address to be updated
// we can wait for all migrations to complete before updating; this includes btc and eth chains.
// we can wait for all migrations to complete before updating; this includes btc and evm chains.
for _, tssMigrator := range tssMigrators {
migratorTx, found := k.GetCrossChainTx(ctx, tssMigrator.MigrationCctxIndex)
if !found {
Expand All @@ -73,18 +74,18 @@ func (k msgServer) UpdateTssAddress(
return &types.MsgUpdateTssAddressResponse{}, nil
}

// GetChainsSupportingTSSMigration returns the chains that support tss migration.
// TSSFundsMigrationChains returns the chains that support tss migration.
Comment thread
kingpinXD marked this conversation as resolved.
// Chains that support tss migration are chains that have the following properties:
// 1. External chains
// 2. Gateway observer
// 3. Consensus is bitcoin or ethereum (Other consensus types are not supported)
func (k *Keeper) GetChainsSupportingTSSMigration(ctx sdk.Context) []chains.Chain {
// 3. VM is EVM, or Consensus is bitcoin (Solana, Sui, TON are excluded as they do not require funds migration)
func (k *Keeper) TSSFundsMigrationChains(ctx sdk.Context) []chains.Chain {
supportedChains := k.zetaObserverKeeper.GetSupportedChains(ctx)
return chains.CombineFilterChains([][]chains.Chain{
chains.FilterChains(supportedChains, []chains.ChainFilter{
chains.FilterExternalChains,
chains.FilterByGateway(chains.CCTXGateway_observers),
chains.FilterByConsensus(chains.Consensus_ethereum),
chains.FilterByVM(chains.Vm_evm),
}...),
chains.FilterChains(supportedChains, []chains.ChainFilter{
chains.FilterExternalChains,
Expand Down
195 changes: 178 additions & 17 deletions x/crosschain/keeper/msg_server_update_tss_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func TestMsgServer_UpdateTssAddress(t *testing.T) {
k.GetObserverKeeper().SetTSSHistory(ctx, tssOld)
k.GetObserverKeeper().SetTSSHistory(ctx, tssNew)
k.GetObserverKeeper().SetTSS(ctx, tssOld)
for _, chain := range k.GetChainsSupportingTSSMigration(ctx) {
for _, chain := range k.TSSFundsMigrationChains(ctx) {
index := chain.Name + "_migration_tx_index"
k.GetObserverKeeper().SetFundMigrator(ctx, types.TssFundMigratorInfo{
ChainId: chain.ChainId,
Expand All @@ -79,7 +79,7 @@ func TestMsgServer_UpdateTssAddress(t *testing.T) {
require.Equal(
t,
len(k.GetObserverKeeper().GetAllTssFundMigrators(ctx)),
len(k.GetChainsSupportingTSSMigration(ctx)),
len(k.TSSFundsMigrationChains(ctx)),
)

msg := crosschaintypes.MsgUpdateTssAddress{
Expand Down Expand Up @@ -110,7 +110,7 @@ func TestMsgServer_UpdateTssAddress(t *testing.T) {

k.GetObserverKeeper().SetTSSHistory(ctx, tssOld)
k.GetObserverKeeper().SetTSS(ctx, tssOld)
for _, chain := range k.GetChainsSupportingTSSMigration(ctx) {
for _, chain := range k.TSSFundsMigrationChains(ctx) {
index := chain.Name + "_migration_tx_index"
k.GetObserverKeeper().SetFundMigrator(ctx, types.TssFundMigratorInfo{
ChainId: chain.ChainId,
Expand All @@ -123,7 +123,7 @@ func TestMsgServer_UpdateTssAddress(t *testing.T) {
require.Equal(
t,
len(k.GetObserverKeeper().GetAllTssFundMigrators(ctx)),
len(k.GetChainsSupportingTSSMigration(ctx)),
len(k.TSSFundsMigrationChains(ctx)),
)

msg := crosschaintypes.MsgUpdateTssAddress{
Expand All @@ -140,7 +140,7 @@ func TestMsgServer_UpdateTssAddress(t *testing.T) {
require.Equal(
t,
len(k.GetObserverKeeper().GetAllTssFundMigrators(ctx)),
len(k.GetChainsSupportingTSSMigration(ctx)),
len(k.TSSFundsMigrationChains(ctx)),
)
})

Expand All @@ -157,7 +157,7 @@ func TestMsgServer_UpdateTssAddress(t *testing.T) {

k.GetObserverKeeper().SetTSSHistory(ctx, tssOld)
k.GetObserverKeeper().SetTSS(ctx, tssOld)
for _, chain := range k.GetChainsSupportingTSSMigration(ctx) {
for _, chain := range k.TSSFundsMigrationChains(ctx) {
index := chain.Name + "_migration_tx_index"
k.GetObserverKeeper().SetFundMigrator(ctx, types.TssFundMigratorInfo{
ChainId: chain.ChainId,
Expand All @@ -170,7 +170,7 @@ func TestMsgServer_UpdateTssAddress(t *testing.T) {
require.Equal(
t,
len(k.GetObserverKeeper().GetAllTssFundMigrators(ctx)),
len(k.GetChainsSupportingTSSMigration(ctx)),
len(k.TSSFundsMigrationChains(ctx)),
)

msg := crosschaintypes.MsgUpdateTssAddress{
Expand All @@ -187,7 +187,7 @@ func TestMsgServer_UpdateTssAddress(t *testing.T) {
require.Equal(
t,
len(k.GetObserverKeeper().GetAllTssFundMigrators(ctx)),
len(k.GetChainsSupportingTSSMigration(ctx)),
len(k.TSSFundsMigrationChains(ctx)),
)
})

Expand All @@ -208,7 +208,7 @@ func TestMsgServer_UpdateTssAddress(t *testing.T) {
setSupportedChain(ctx, zk, getValidEthChainIDWithIndex(t, 0), getValidEthChainIDWithIndex(t, 1))

// set a single migrator while there are 2 supported chains
chain := k.GetChainsSupportingTSSMigration(ctx)[0]
chain := k.TSSFundsMigrationChains(ctx)[0]
index := chain.Name + "_migration_tx_index"
k.GetObserverKeeper().SetFundMigrator(ctx, types.TssFundMigratorInfo{
ChainId: chain.ChainId,
Expand Down Expand Up @@ -255,7 +255,7 @@ func TestMsgServer_UpdateTssAddress(t *testing.T) {
k.GetObserverKeeper().SetTSS(ctx, tssOld)
setSupportedChain(ctx, zk, getValidEthChainIDWithIndex(t, 0), getValidEthChainIDWithIndex(t, 1))

for _, chain := range k.GetChainsSupportingTSSMigration(ctx) {
for _, chain := range k.TSSFundsMigrationChains(ctx) {
index := chain.Name + "_migration_tx_index"
k.GetObserverKeeper().SetFundMigrator(ctx, types.TssFundMigratorInfo{
ChainId: chain.ChainId,
Expand Down Expand Up @@ -302,7 +302,7 @@ func TestMsgServer_UpdateTssAddress(t *testing.T) {
k.GetObserverKeeper().SetTSS(ctx, tssOld)
setSupportedChain(ctx, zk, getValidEthChainIDWithIndex(t, 0), getValidEthChainIDWithIndex(t, 1))

for _, chain := range k.GetChainsSupportingTSSMigration(ctx) {
for _, chain := range k.TSSFundsMigrationChains(ctx) {
index := chain.Name + "_migration_tx_index"
k.GetObserverKeeper().SetFundMigrator(ctx, types.TssFundMigratorInfo{
ChainId: chain.ChainId,
Expand Down Expand Up @@ -332,7 +332,7 @@ func TestMsgServer_UpdateTssAddress(t *testing.T) {
}

func TestKeeper_GetChainsSupportingTSSMigration(t *testing.T) {
t.Run("should return only ethereum and bitcoin chains", func(t *testing.T) {
t.Run("should return only EVM and bitcoin chains for mainnet ", func(t *testing.T) {
k, ctx, _, zk := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{})
chainList := chains.ExternalChainList([]chains.Chain{})
var chainParamsList types.ChainParamsList
Expand All @@ -344,12 +344,173 @@ func TestKeeper_GetChainsSupportingTSSMigration(t *testing.T) {
}
zk.ObserverKeeper.SetChainParamsList(ctx, chainParamsList)

chainsSupportingMigration := k.GetChainsSupportingTSSMigration(ctx)
chainsSupportingMigration := k.TSSFundsMigrationChains(ctx)
for _, chain := range chainsSupportingMigration {
require.NotEqual(t, chain.Consensus, chains.Consensus_solana_consensus)
require.NotEqual(t, chain.Consensus, chains.Consensus_op_stack)
require.NotEqual(t, chain.Consensus, chains.Consensus_tendermint)
require.Equal(t, chain.IsExternal, true)
// Should not include non-EVM, non-bitcoin chains
require.NotEqual(t, chain.Consensus, chains.Consensus_solana_consensus,
"chain %s should not have solana consensus", chain.Name)
require.NotEqual(t, chain.Consensus, chains.Consensus_tendermint,
"chain %s should not have tendermint consensus", chain.Name)
require.NotEqual(t, chain.Consensus, chains.Consensus_sui_consensus,
"chain %s should not have sui consensus", chain.Name)
require.NotEqual(t, chain.Consensus, chains.Consensus_catchain_consensus,
"chain %s should not have catchain consensus", chain.Name)
require.True(t, chain.IsExternal, "chain %s should be external", chain.Name)
// Should be EVM or bitcoin
require.True(t,
chain.Vm == chains.Vm_evm || chain.Consensus == chains.Consensus_bitcoin,
"chain %s should be EVM or bitcoin", chain.Name)
}
})

t.Run("should return correct mainnet chains requiring migration of TSS funds", func(t *testing.T) {
k, ctx, _, zk := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{})

// Set up all mainnet chains as supported
mainnetChains := []chains.Chain{
chains.Ethereum, // chain_id: 1, Vm_evm
chains.BscMainnet, // chain_id: 56, Vm_evm
chains.BitcoinMainnet, // chain_id: 8332, Vm_no_vm, bitcoin consensus
chains.ZetaChainMainnet, // chain_id: 7000, Vm_evm but not external, zevm gateway (excluded)
chains.Polygon, // chain_id: 137, Vm_evm
chains.BaseMainnet, // chain_id: 8453, Vm_evm
chains.SolanaMainnet, // chain_id: 900, Vm_svm No funds migration needed (excluded)
chains.ArbitrumMainnet, // chain_id: 42161, Vm_evm
chains.AvalancheMainnet, // chain_id: 43114, Vm_evm
chains.SuiMainnet, // chain_id: 105, Vm_mvm_sui No funds migration needed (excluded)
chains.TONMainnet, // chain_id: 2015140, Vm_tvm No funds migration needed (excluded)
}

var chainParamsList types.ChainParamsList
for _, chain := range mainnetChains {
chainParamsList.ChainParams = append(
chainParamsList.ChainParams,
sample.ChainParamsSupported(chain.ChainId),
)
}
zk.ObserverKeeper.SetChainParamsList(ctx, chainParamsList)

chainsSupportingMigration := k.TSSFundsMigrationChains(ctx)

expectedChainIDs := map[int64]bool{
chains.Ethereum.ChainId: true,
chains.BscMainnet.ChainId: true,
chains.BitcoinMainnet.ChainId: true,
chains.Polygon.ChainId: true,
chains.BaseMainnet.ChainId: true,
chains.ArbitrumMainnet.ChainId: true,
chains.AvalancheMainnet.ChainId: true,
}

require.Equal(t, len(expectedChainIDs), len(chainsSupportingMigration),
"expected %d chains, got %d", len(expectedChainIDs), len(chainsSupportingMigration))

for _, chain := range chainsSupportingMigration {
require.True(t, expectedChainIDs[chain.ChainId],
"unexpected chain in result: %s (chain_id: %d, vm: %s)",
chain.Name, chain.ChainId, chain.Vm)

require.True(t, chain.IsExternal, "chain %s should be external", chain.Name)
require.Equal(t, chains.CCTXGateway_observers, chain.CctxGateway,
"chain %s should have observers gateway", chain.Name)
require.True(t,
chain.Vm == chains.Vm_evm || chain.Consensus == chains.Consensus_bitcoin,
"chain %s should be EVM or bitcoin, got vm=%s consensus=%s", chain.Name, chain.Vm, chain.Consensus)
}

excludedChainIDs := []int64{
chains.ZetaChainMainnet.ChainId, // not external, zevm gateway
chains.SolanaMainnet.ChainId, // Vm_svm
chains.SuiMainnet.ChainId, // Vm_mvm_sui
chains.TONMainnet.ChainId, // Vm_tvm
}

resultChainIDs := make(map[int64]bool)
for _, chain := range chainsSupportingMigration {
resultChainIDs[chain.ChainId] = true
}

for _, excludedID := range excludedChainIDs {
require.False(t, resultChainIDs[excludedID],
"chain with ID %d should be excluded from TSS migration", excludedID)
}
})

t.Run("should return correct testnet chains supporting TSS migration", func(t *testing.T) {
k, ctx, _, zk := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{})

testnetChains := []chains.Chain{
chains.BscTestnet, // chain_id: 97, Vm_evm
chains.Sepolia, // chain_id: 11155111, Vm_evm
chains.BitcoinSignetTestnet, // chain_id: 18333, Vm_no_vm, bitcoin consensus
chains.BitcoinTestnet4, // chain_id: 18334, Vm_no_vm, bitcoin consensus
chains.Amoy, // chain_id: 80002, Vm_evm
chains.BaseSepolia, // chain_id: 84532, Vm_evm
chains.ArbitrumSepolia, // chain_id: 421614, Vm_evm
chains.AvalancheTestnet, // chain_id: 43113, Vm_evm
chains.WorldTestnet, // chain_id: 4801, Vm_evm
chains.ZetaChainTestnet, // not external, zevm gateway (excluded)
chains.SolanaDevnet, // Vm_svm (excluded)
chains.SuiTestnet, // Vm_mvm_sui (excluded)
chains.TONTestnet, // Vm_tvm (excluded)
}

var chainParamsList types.ChainParamsList
for _, chain := range testnetChains {
chainParamsList.ChainParams = append(
chainParamsList.ChainParams,
sample.ChainParamsSupported(chain.ChainId),
)
}
zk.ObserverKeeper.SetChainParamsList(ctx, chainParamsList)

chainsSupportingMigration := k.TSSFundsMigrationChains(ctx)

expectedChainIDs := map[int64]bool{
chains.BscTestnet.ChainId: true,
chains.Sepolia.ChainId: true,
chains.BitcoinSignetTestnet.ChainId: true,
chains.BitcoinTestnet4.ChainId: true,
chains.Amoy.ChainId: true,
chains.BaseSepolia.ChainId: true,
chains.ArbitrumSepolia.ChainId: true,
chains.AvalancheTestnet.ChainId: true,
chains.WorldTestnet.ChainId: true,
}

// Verify the count matches expected
require.Equal(t, len(expectedChainIDs), len(chainsSupportingMigration),
"expected %d chains, got %d", len(expectedChainIDs), len(chainsSupportingMigration))

for _, chain := range chainsSupportingMigration {
require.True(t, expectedChainIDs[chain.ChainId],
"unexpected chain in result: %s (chain_id: %d, vm: %s)",
chain.Name, chain.ChainId, chain.Vm)

require.True(t, chain.IsExternal, "chain %s should be external", chain.Name)
require.Equal(t, chains.CCTXGateway_observers, chain.CctxGateway,
"chain %s should have observers gateway", chain.Name)
require.True(t,
chain.Vm == chains.Vm_evm || chain.Consensus == chains.Consensus_bitcoin,
"chain %s should be EVM or bitcoin, got vm=%s consensus=%s", chain.Name, chain.Vm, chain.Consensus)
}

// Verify excluded chains are not in the result
excludedChainIDs := []int64{
chains.ZetaChainTestnet.ChainId, // not external, zevm gateway
chains.SolanaDevnet.ChainId, // Vm_svm
chains.SuiTestnet.ChainId, // Vm_mvm_sui
chains.TONTestnet.ChainId, // Vm_tvm
}

resultChainIDs := make(map[int64]bool)
for _, chain := range chainsSupportingMigration {
resultChainIDs[chain.ChainId] = true
}

for _, excludedID := range excludedChainIDs {
require.False(t, resultChainIDs[excludedID],
"chain with ID %d should be excluded from TSS migration", excludedID)
}
})
}
2 changes: 1 addition & 1 deletion x/crosschain/simulation/operation_update_tss_address.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func SimulateMsgUpdateTssAddress(k keeper.Keeper) simtypes.Operation {
authAccount := k.GetAuthKeeper().GetAccount(ctx, policyAccount.Address)
spendable := k.GetBankKeeper().SpendableCoins(ctx, authAccount.GetAddress())

supportedChains := k.GetChainsSupportingTSSMigration(ctx)
supportedChains := k.TSSFundsMigrationChains(ctx)
if len(supportedChains) == 0 {
return simtypes.NoOpMsg(
types.ModuleName,
Expand Down
Loading