From 740d48a92a7206fb494ce555be1b48f6eeff1b2e Mon Sep 17 00:00:00 2001 From: aman035 Date: Thu, 5 Mar 2026 17:10:40 +0530 Subject: [PATCH 1/6] fix: evm client to fetch vault address --- universalClient/chains/evm/client.go | 79 ++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 21 deletions(-) diff --git a/universalClient/chains/evm/client.go b/universalClient/chains/evm/client.go index 4f921912..9d1d260e 100644 --- a/universalClient/chains/evm/client.go +++ b/universalClient/chains/evm/client.go @@ -6,6 +6,8 @@ import ( "strings" "time" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/rs/zerolog" "github.com/pushchain/push-chain-node/universalClient/chains/common" @@ -195,11 +197,26 @@ func (c *Client) initializeComponents() error { eventStartFrom = c.chainConfig.EventStartFrom } + // Fetch vault address from gateway contract + var vaultAddress string + var vaultAddr ethcommon.Address + fetchCtx, fetchCancel := context.WithTimeout(c.ctx, 15*time.Second) + vaultAddr, err := FetchVaultAddress(fetchCtx, c.rpcClient, ethcommon.HexToAddress(c.registryConfig.GatewayAddress)) + fetchCancel() + if err != nil { + c.logger.Warn().Err(err).Msg("failed to fetch vault address from gateway, vault events will not be monitored") + } else { + vaultAddress = vaultAddr.Hex() + c.logger.Info().Str("vault_address", vaultAddress).Msg("vault address fetched from gateway") + } + eventListener, err := NewEventListener( c.rpcClient, c.registryConfig.GatewayAddress, + vaultAddress, c.registryConfig.Chain, c.registryConfig.GatewayMethods, + c.registryConfig.VaultMethods, c.database, eventPollingSeconds, eventStartFrom, @@ -209,6 +226,25 @@ func (c *Client) initializeComponents() error { return fmt.Errorf("failed to create event listener: %w", err) } c.eventListener = eventListener + + // Create txBuilder + chainIDInt, err := parseEVMChainID(c.chainIDStr) + if err != nil { + return fmt.Errorf("failed to parse chain ID for txBuilder: %w", err) + } + + txBuilder, err := NewTxBuilder( + c.rpcClient, + c.chainIDStr, + chainIDInt, + c.registryConfig.GatewayAddress, + vaultAddr, + c.logger, + ) + if err != nil { + return fmt.Errorf("failed to create txBuilder: %w", err) + } + c.txBuilder = txBuilder } // Apply defaults for all configuration values @@ -236,27 +272,6 @@ func (c *Client) initializeComponents() error { ) } - // Create txBuilder if gateway is configured - if c.registryConfig != nil && c.registryConfig.GatewayAddress != "" { - // Parse chain ID to integer - chainIDInt, err := parseEVMChainID(c.chainIDStr) - if err != nil { - return fmt.Errorf("failed to parse chain ID for txBuilder: %w", err) - } - - txBuilder, err := NewTxBuilder( - c.rpcClient, - c.chainIDStr, - chainIDInt, - c.registryConfig.GatewayAddress, - c.logger, - ) - if err != nil { - return fmt.Errorf("failed to create txBuilder: %w", err) - } - c.txBuilder = txBuilder - } - return nil } @@ -363,3 +378,25 @@ func parseEVMChainID(caip2 string) (int64, error) { return chainID, nil } + +// FetchVaultAddress calls the gateway's VAULT() public getter to retrieve the vault address. +func FetchVaultAddress(ctx context.Context, rpcClient *RPCClient, gatewayAddress ethcommon.Address) (ethcommon.Address, error) { + // vaultCallSelector is the 4-byte selector for VAULT() public getter + vaultCallSelector := crypto.Keccak256([]byte("VAULT()"))[:4] + + result, err := rpcClient.CallContract(ctx, gatewayAddress, vaultCallSelector, nil) + if err != nil { + return ethcommon.Address{}, fmt.Errorf("VAULT() call failed: %w", err) + } + + if len(result) < 32 { + return ethcommon.Address{}, fmt.Errorf("VAULT() returned invalid data (len=%d)", len(result)) + } + + addr := ethcommon.BytesToAddress(result[12:32]) + if addr == (ethcommon.Address{}) { + return ethcommon.Address{}, fmt.Errorf("VAULT() returned zero address") + } + + return addr, nil +} From ff7dcbdcbf6c7622e871cfcb5e8c3b2538a56fac Mon Sep 17 00:00:00 2001 From: aman035 Date: Thu, 5 Mar 2026 17:11:39 +0530 Subject: [PATCH 2/6] fix: event listening & parsing --- universalClient/chains/evm/event_listener.go | 33 ++++++++++++++----- universalClient/chains/evm/event_parser.go | 12 ++++--- .../chains/evm/event_parser_test.go | 6 ++-- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/universalClient/chains/evm/event_listener.go b/universalClient/chains/evm/event_listener.go index 552f4f1e..97af5967 100644 --- a/universalClient/chains/evm/event_listener.go +++ b/universalClient/chains/evm/event_listener.go @@ -16,7 +16,7 @@ import ( uregistrytypes "github.com/pushchain/push-chain-node/x/uregistry/types" ) -// EventListener listens for gateway events on EVM chains and stores them in the database +// EventListener listens for gateway and vault events on EVM chains and stores them in the database type EventListener struct { // Core dependencies rpcClient *RPCClient @@ -25,6 +25,7 @@ type EventListener struct { // Configuration gatewayAddress string + vaultAddress string chainID string eventTopics []ethcommon.Hash topicToEventType map[ethcommon.Hash]string @@ -42,8 +43,10 @@ type EventListener struct { func NewEventListener( rpcClient *RPCClient, gatewayAddress string, + vaultAddress string, chainID string, gatewayMethods []*uregistrytypes.GatewayMethods, + vaultMethods []*uregistrytypes.VaultMethods, database *db.DB, eventPollingSeconds int, eventStartFrom *int64, @@ -58,15 +61,16 @@ func NewEventListener( } // Build event topics for filtering - eventTopics := make([]ethcommon.Hash, 0, 3) + eventTopics := make([]ethcommon.Hash, 0, 4) topicToEventType := make(map[ethcommon.Hash]string) + + // Gateway event topics for _, method := range gatewayMethods { if method.EventIdentifier == "" { continue } switch method.Name { case EventTypeSendFunds, - EventTypeExecuteUniversalTx, EventTypeRevertUniversalTx: topic := ethcommon.HexToHash(method.EventIdentifier) eventTopics = append(eventTopics, topic) @@ -74,11 +78,25 @@ func NewEventListener( } } + // Vault event topics + for _, method := range vaultMethods { + if method.EventIdentifier == "" { + continue + } + switch method.Name { + case EventTypeVaultUniversalTxFinalized: + topic := ethcommon.HexToHash(method.EventIdentifier) + eventTopics = append(eventTopics, topic) + topicToEventType[topic] = method.Name + } + } + return &EventListener{ rpcClient: rpcClient, chainStore: common.NewChainStore(database), database: database, gatewayAddress: gatewayAddress, + vaultAddress: vaultAddress, chainID: chainID, eventTopics: eventTopics, topicToEventType: topicToEventType, @@ -251,14 +269,14 @@ func (el *EventListener) processBlockChunk( fromBlock, toBlock uint64, topics []ethcommon.Hash, ) error { - // Parse gateway address - gatewayAddr := ethcommon.HexToAddress(el.gatewayAddress) + // Build address filter: gateway + vault + addresses := []ethcommon.Address{ethcommon.HexToAddress(el.gatewayAddress), ethcommon.HexToAddress(el.vaultAddress)} // Create filter query query := ethereum.FilterQuery{ FromBlock: big.NewInt(int64(fromBlock)), ToBlock: big.NewInt(int64(toBlock)), - Addresses: []ethcommon.Address{gatewayAddr}, + Addresses: addresses, Topics: [][]ethcommon.Hash{topics}, } @@ -274,8 +292,7 @@ func (el *EventListener) processBlockChunk( Uint64("from_block", fromBlock). Uint64("to_block", toBlock). Int("logs_found", len(logs)). - Str("gateway_address", el.gatewayAddress). - Msg("found gateway events") + Msg("found contract events") } // Process each log diff --git a/universalClient/chains/evm/event_parser.go b/universalClient/chains/evm/event_parser.go index 804c2e1f..765f134f 100644 --- a/universalClient/chains/evm/event_parser.go +++ b/universalClient/chains/evm/event_parser.go @@ -21,9 +21,13 @@ import ( // Event type constants matching gateway method names in chain config. const ( - EventTypeSendFunds = "sendFunds" - EventTypeExecuteUniversalTx = "executeUniversalTx" // UniversalTxExecuted on-chain event - EventTypeRevertUniversalTx = "revertUniversalTx" // RevertUniversalTx on-chain event + EventTypeSendFunds = "sendFunds" + EventTypeRevertUniversalTx = "revertUniversalTx" // RevertUniversalTx on-chain event +) + +// Vault event type constants matching vault method names in chain config. +const ( + EventTypeVaultUniversalTxFinalized = "vaultUniversalTxFinalized" // VaultUniversalTxFinalized on-chain event ) // ParseEvent parses a log into a store.Event based on the event type. @@ -36,7 +40,7 @@ func ParseEvent(log *types.Log, eventType string, chainID string, logger zerolog switch eventType { case EventTypeSendFunds: return parseSendFundsEvent(log, chainID, logger) - case EventTypeExecuteUniversalTx, EventTypeRevertUniversalTx: + case EventTypeRevertUniversalTx, EventTypeVaultUniversalTxFinalized: // Both events share the same topic layout: Topics[1]=txID, Topics[2]=universalTxID. return parseOutboundObservationEvent(log, chainID, logger) default: diff --git a/universalClient/chains/evm/event_parser_test.go b/universalClient/chains/evm/event_parser_test.go index 54454509..7017130d 100644 --- a/universalClient/chains/evm/event_parser_test.go +++ b/universalClient/chains/evm/event_parser_test.go @@ -100,7 +100,7 @@ func TestParseGatewayEvent(t *testing.T) { }, { name: "returns nil for outboundObservation with insufficient topics", - eventType: EventTypeExecuteUniversalTx, + eventType: EventTypeVaultUniversalTxFinalized, log: &types.Log{ Address: gatewayAddr, Topics: []ethcommon.Hash{eventTopic}, // Only 1 topic, need 3 for outbound @@ -308,7 +308,7 @@ func TestParseOutboundObservationEvent(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - event := ParseEvent(tt.log, EventTypeExecuteUniversalTx, chainID, logger) + event := ParseEvent(tt.log, EventTypeVaultUniversalTxFinalized, chainID, logger) if tt.wantEvent { require.NotNil(t, event) @@ -354,7 +354,7 @@ func TestParseGatewayEvent_OutboundObservation(t *testing.T) { BlockNumber: 77777, } - event := ParseEvent(log, EventTypeExecuteUniversalTx, config.Chain, logger) + event := ParseEvent(log, EventTypeVaultUniversalTxFinalized, config.Chain, logger) require.NotNil(t, event) assert.Equal(t, common.EventTypeOutbound, event.Type) From 229c32ddfee34705256627d8c32cbbd41ebc2366 Mon Sep 17 00:00:00 2001 From: aman035 Date: Thu, 5 Mar 2026 17:22:47 +0530 Subject: [PATCH 3/6] fix: client should error if unable to fetch vault --- universalClient/chains/evm/client.go | 10 +++------- universalClient/chains/evm/client_test.go | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/universalClient/chains/evm/client.go b/universalClient/chains/evm/client.go index 9d1d260e..ea5f6d9c 100644 --- a/universalClient/chains/evm/client.go +++ b/universalClient/chains/evm/client.go @@ -198,22 +198,18 @@ func (c *Client) initializeComponents() error { } // Fetch vault address from gateway contract - var vaultAddress string - var vaultAddr ethcommon.Address fetchCtx, fetchCancel := context.WithTimeout(c.ctx, 15*time.Second) vaultAddr, err := FetchVaultAddress(fetchCtx, c.rpcClient, ethcommon.HexToAddress(c.registryConfig.GatewayAddress)) fetchCancel() if err != nil { - c.logger.Warn().Err(err).Msg("failed to fetch vault address from gateway, vault events will not be monitored") - } else { - vaultAddress = vaultAddr.Hex() - c.logger.Info().Str("vault_address", vaultAddress).Msg("vault address fetched from gateway") + return fmt.Errorf("failed to fetch vault address from gateway: %w", err) } + c.logger.Info().Str("vault_address", vaultAddr.Hex()).Msg("vault address fetched from gateway") eventListener, err := NewEventListener( c.rpcClient, c.registryConfig.GatewayAddress, - vaultAddress, + vaultAddr.Hex(), c.registryConfig.Chain, c.registryConfig.GatewayMethods, c.registryConfig.VaultMethods, diff --git a/universalClient/chains/evm/client_test.go b/universalClient/chains/evm/client_test.go index 26f0b41a..380df1d0 100644 --- a/universalClient/chains/evm/client_test.go +++ b/universalClient/chains/evm/client_test.go @@ -147,11 +147,25 @@ func TestClientStartStop(t *testing.T) { logger := zerolog.New(zerolog.NewTestWriter(t)) t.Run("Start with mock server", func(t *testing.T) { + // Mock vault address: 0x000...abc (left-padded to 32 bytes) + mockVaultResult := "0x000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + // Create a mock HTTP server server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Mock eth_chainId response w.Header().Set("Content-Type", "application/json") - w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x1"}`)) + + // Parse the JSON-RPC method + body := make([]byte, r.ContentLength) + r.Body.Read(body) + bodyStr := string(body) + + if strings.Contains(bodyStr, "eth_call") { + // Return a mock vault address for VAULT() call + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"` + mockVaultResult + `"}`)) + } else { + // Default: eth_chainId response + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x1"}`)) + } })) defer server.Close() From 3ee3b100e5a292a4496454f7a7ecbe219e391e51 Mon Sep 17 00:00:00 2001 From: aman035 Date: Thu, 5 Mar 2026 17:23:14 +0530 Subject: [PATCH 4/6] refactor: txbuilder --- universalClient/chains/evm/tx_builder.go | 145 ++---------------- universalClient/chains/evm/tx_builder_test.go | 60 +------- 2 files changed, 14 insertions(+), 191 deletions(-) diff --git a/universalClient/chains/evm/tx_builder.go b/universalClient/chains/evm/tx_builder.go index 2e516942..15a985b1 100644 --- a/universalClient/chains/evm/tx_builder.go +++ b/universalClient/chains/evm/tx_builder.go @@ -7,8 +7,6 @@ import ( "math/big" "strconv" "strings" - "sync" - "time" "github.com/ethereum/go-ethereum/accounts/abi" ethcommon "github.com/ethereum/go-ethereum/common" @@ -23,13 +21,6 @@ import ( // DefaultGasLimit is used when gas limit is not provided in the outbound event data const DefaultGasLimit = 500000 -// vaultRefreshInterval is how often we re-fetch the vault address from the gateway -const vaultRefreshInterval = 10 * time.Minute - -// vaultCallSelector is the 4-byte selector for VAULT() public getter -// keccak256("VAULT()") = 0x71e99dc2... -var vaultCallSelector = crypto.Keccak256([]byte("VAULT()"))[:4] - // RevertInstructions represents the struct for revert instruction in contracts // Matches: struct RevertInstructions { address revertRecipient; bytes revertMsg; } type RevertInstructions struct { @@ -38,8 +29,6 @@ type RevertInstructions struct { } // TxBuilder implements OutboundTxBuilder for EVM chains using Vault + Gateway contracts. -// The vault address is fetched from the gateway's VAULT() public variable and refreshed -// periodically so that vault upgrades are picked up automatically. // // Routing: // - FUNDS, FUNDS_AND_PAYLOAD, PAYLOAD → Vault.finalizeUniversalTx @@ -50,23 +39,18 @@ type TxBuilder struct { chainID string chainIDInt int64 gatewayAddress ethcommon.Address - logger zerolog.Logger - - // vault address cache - vaultMu sync.RWMutex vaultAddress ethcommon.Address - vaultFetchedAt time.Time + logger zerolog.Logger } // NewTxBuilder creates a new EVM transaction builder for Vault + Gateway. -// It attempts to fetch the vault address from the gateway contract's VAULT() public variable. -// If the initial fetch fails, the builder is still created — individual requests will fail -// until the vault address is successfully fetched on the next refresh attempt. +// The vault address is provided by the caller (fetched from the gateway by the client). func NewTxBuilder( rpcClient *RPCClient, chainID string, chainIDInt int64, gatewayAddress string, + vaultAddress ethcommon.Address, logger zerolog.Logger, ) (*TxBuilder, error) { if rpcClient == nil { @@ -89,111 +73,18 @@ func NewTxBuilder( chainID: chainID, chainIDInt: chainIDInt, gatewayAddress: gwAddr, + vaultAddress: vaultAddress, logger: logger.With().Str("component", "evm_tx_builder").Str("chain", chainID).Logger(), } - // Best-effort fetch of vault address from gateway at startup - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() - - vaultAddr, err := tb.fetchVaultAddress(ctx) - if err != nil { - tb.logger.Warn().Err(err). - Str("gateway", gwAddr.Hex()). - Msg("failed to fetch vault address from gateway on init, will retry on first request") - } else { - tb.vaultAddress = vaultAddr - tb.vaultFetchedAt = time.Now() - tb.logger.Info(). - Str("vault", vaultAddr.Hex()). - Str("gateway", gwAddr.Hex()). - Msg("vault address fetched from gateway") - } + tb.logger.Info(). + Str("vault", vaultAddress.Hex()). + Str("gateway", gwAddr.Hex()). + Msg("tx builder initialized") return tb, nil } -// getVaultAddress returns the cached vault address, refreshing in the background if stale. -// Returns an error if the vault address has never been successfully fetched. -// If stale, triggers an async refresh but returns the last known good address. -func (tb *TxBuilder) getVaultAddress() (ethcommon.Address, error) { - tb.vaultMu.RLock() - addr := tb.vaultAddress - fetchedAt := tb.vaultFetchedAt - tb.vaultMu.RUnlock() - - // Never fetched successfully - if addr == (ethcommon.Address{}) { - // Try a synchronous fetch before failing - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - newAddr, err := tb.fetchVaultAddress(ctx) - if err != nil { - return ethcommon.Address{}, fmt.Errorf("vault address not available: %w", err) - } - - tb.vaultMu.Lock() - tb.vaultAddress = newAddr - tb.vaultFetchedAt = time.Now() - tb.vaultMu.Unlock() - - tb.logger.Info().Str("vault", newAddr.Hex()).Msg("vault address fetched from gateway (deferred)") - return newAddr, nil - } - - // Stale — refresh in background, return current - if time.Since(fetchedAt) > vaultRefreshInterval { - go tb.tryRefreshVaultAddress() - } - - return addr, nil -} - -// tryRefreshVaultAddress attempts to refresh the vault address from the gateway. -// On failure, the stale address continues to be used. -func (tb *TxBuilder) tryRefreshVaultAddress() { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - newAddr, err := tb.fetchVaultAddress(ctx) - if err != nil { - tb.logger.Warn().Err(err).Msg("failed to refresh vault address from gateway, using stale") - return - } - - tb.vaultMu.Lock() - oldAddr := tb.vaultAddress - tb.vaultAddress = newAddr - tb.vaultFetchedAt = time.Now() - tb.vaultMu.Unlock() - - if oldAddr != newAddr { - tb.logger.Info(). - Str("old_vault", oldAddr.Hex()). - Str("new_vault", newAddr.Hex()). - Msg("vault address updated from gateway") - } -} - -// fetchVaultAddress calls the gateway's VAULT() public getter to retrieve the vault address. -func (tb *TxBuilder) fetchVaultAddress(ctx context.Context) (ethcommon.Address, error) { - result, err := tb.rpcClient.CallContract(ctx, tb.gatewayAddress, vaultCallSelector, nil) - if err != nil { - return ethcommon.Address{}, fmt.Errorf("VAULT() call failed: %w", err) - } - - if len(result) < 32 { - return ethcommon.Address{}, fmt.Errorf("VAULT() returned invalid data (len=%d)", len(result)) - } - - addr := ethcommon.BytesToAddress(result[12:32]) - if addr == (ethcommon.Address{}) { - return ethcommon.Address{}, fmt.Errorf("VAULT() returned zero address") - } - - return addr, nil -} // GetOutboundSigningRequest creates a signing request from outbound event data func (tb *TxBuilder) GetOutboundSigningRequest( @@ -380,28 +271,16 @@ func (tb *TxBuilder) resolveTxParams(funcName string, assetAddr ethcommon.Addres return amount, tb.gatewayAddress, nil case "finalizeUniversalTx": - vaultAddr, err := tb.getVaultAddress() - if err != nil { - return nil, ethcommon.Address{}, fmt.Errorf("cannot route to vault: %w", err) - } if isNative { - return amount, vaultAddr, nil + return amount, tb.vaultAddress, nil } - return big.NewInt(0), vaultAddr, nil + return big.NewInt(0), tb.vaultAddress, nil case "revertUniversalTxToken": - vaultAddr, err := tb.getVaultAddress() - if err != nil { - return nil, ethcommon.Address{}, fmt.Errorf("cannot route to vault: %w", err) - } - return big.NewInt(0), vaultAddr, nil + return big.NewInt(0), tb.vaultAddress, nil default: - vaultAddr, err := tb.getVaultAddress() - if err != nil { - return nil, ethcommon.Address{}, fmt.Errorf("cannot route to vault: %w", err) - } - return big.NewInt(0), vaultAddr, nil + return big.NewInt(0), tb.vaultAddress, nil } } diff --git a/universalClient/chains/evm/tx_builder_test.go b/universalClient/chains/evm/tx_builder_test.go index 294a5ab9..b4f8d30b 100644 --- a/universalClient/chains/evm/tx_builder_test.go +++ b/universalClient/chains/evm/tx_builder_test.go @@ -4,7 +4,6 @@ import ( "encoding/hex" "math/big" "testing" - "time" "github.com/ethereum/go-ethereum/accounts/abi" ethcommon "github.com/ethereum/go-ethereum/common" @@ -32,9 +31,8 @@ func newTestTxBuilder(t *testing.T) *TxBuilder { chainID: "eth_sepolia", chainIDInt: 11155111, gatewayAddress: gwAddr, - vaultAddress: vAddr, - vaultFetchedAt: time.Now(), - logger: logger.With().Str("component", "evm_tx_builder").Logger(), + vaultAddress: vAddr, + logger: logger.With().Str("component", "evm_tx_builder").Logger(), } } @@ -588,60 +586,6 @@ func TestFinalizeUniversalTxUnifiedEncoding(t *testing.T) { } } -func TestGetVaultAddressReturnsCached(t *testing.T) { - builder := newTestTxBuilder(t) - - // Vault was set at construction with time.Now(), so it's fresh - addr, err := builder.getVaultAddress() - require.NoError(t, err) - assert.Equal(t, ethcommon.HexToAddress(testVaultAddress), addr) -} - -func TestGetVaultAddressStaleTriggersRefresh(t *testing.T) { - builder := newTestTxBuilder(t) - - // Make the cache stale - builder.vaultFetchedAt = time.Now().Add(-2 * vaultRefreshInterval) - - // Should still return the cached address (refresh is async, and will fail - // since we have no real RPC — that's fine, stale address is kept) - addr, err := builder.getVaultAddress() - require.NoError(t, err) - assert.Equal(t, ethcommon.HexToAddress(testVaultAddress), addr) - - // Give the goroutine a moment to attempt and fail refresh - time.Sleep(50 * time.Millisecond) - - // Address should still be the same (failed refresh keeps stale) - addr, err = builder.getVaultAddress() - require.NoError(t, err) - assert.Equal(t, ethcommon.HexToAddress(testVaultAddress), addr) -} - -func TestGetVaultAddressFailsWhenNeverFetched(t *testing.T) { - logger := zerolog.Nop() - // Create builder with zero vault address (simulates failed init fetch) - builder := &TxBuilder{ - rpcClient: &RPCClient{}, - chainID: "eth_sepolia", - chainIDInt: 11155111, - gatewayAddress: ethcommon.HexToAddress("0x1234567890123456789012345678901234567890"), - logger: logger.With().Str("component", "evm_tx_builder").Logger(), - } - - // Should fail since vault was never fetched and RPC is not real - _, err := builder.getVaultAddress() - assert.Error(t, err) - assert.Contains(t, err.Error(), "vault address not available") -} - -func TestVaultCallSelector(t *testing.T) { - // Verify the VAULT() selector is correct - expected := crypto.Keccak256([]byte("VAULT()"))[:4] - assert.Equal(t, expected, vaultCallSelector) - t.Logf("VAULT() selector: 0x%x", vaultCallSelector) -} - // ============================================================================= // Sepolia V2 Simulation Tests // From 390d8cab42b81a36ba378b7569474ec88face65f Mon Sep 17 00:00:00 2001 From: aman035 Date: Fri, 6 Mar 2026 14:14:22 +0530 Subject: [PATCH 5/6] fix: config equal checks --- universalClient/chains/chains.go | 79 +++++++++++++++----- universalClient/chains/chains_test.go | 103 +++++++++++--------------- 2 files changed, 102 insertions(+), 80 deletions(-) diff --git a/universalClient/chains/chains.go b/universalClient/chains/chains.go index cf4702d3..fd74f257 100644 --- a/universalClient/chains/chains.go +++ b/universalClient/chains/chains.go @@ -228,12 +228,6 @@ const ( func (c *Chains) determineChainAction(cfg *uregistrytypes.ChainConfig) chainAction { chainID := cfg.Chain - // Skip disabled chains - if cfg.Enabled == nil || (!cfg.Enabled.IsInboundEnabled && !cfg.Enabled.IsOutboundEnabled) { - c.logger.Debug().Str("chain", chainID).Msg("chain is disabled, skipping") - return chainActionSkip - } - // Check if chain exists c.chainsMu.RLock() _, exists := c.chains[chainID] @@ -485,24 +479,71 @@ func sanitizeChainID(chainID string) string { return result } -// configsEqual compares two chain configurations +// configsEqual compares two chain configurations for fields relevant to the universal client func configsEqual(a, b *uregistrytypes.ChainConfig) bool { if a == nil || b == nil { return a == b } - // Handle Enabled field comparison - enabledEqual := false - if a.Enabled == nil && b.Enabled == nil { - enabledEqual = true - } else if a.Enabled != nil && b.Enabled != nil { - enabledEqual = a.Enabled.IsInboundEnabled == b.Enabled.IsInboundEnabled && - a.Enabled.IsOutboundEnabled == b.Enabled.IsOutboundEnabled + if a.Chain != b.Chain || + a.VmType != b.VmType || + a.GatewayAddress != b.GatewayAddress { + return false + } + + // Compare gateway methods + if !gatewayMethodsEqual(a.GatewayMethods, b.GatewayMethods) { + return false + } + + // Compare vault methods + if !vaultMethodsEqual(a.VaultMethods, b.VaultMethods) { + return false } - // Compare relevant fields - return a.Chain == b.Chain && - a.VmType == b.VmType && - a.GatewayAddress == b.GatewayAddress && - enabledEqual + // Compare block confirmation + if !blockConfirmationEqual(a.BlockConfirmation, b.BlockConfirmation) { + return false + } + + return true +} + +func gatewayMethodsEqual(a, b []*uregistrytypes.GatewayMethods) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i].Name != b[i].Name || + a[i].EventIdentifier != b[i].EventIdentifier || + a[i].ConfirmationType != b[i].ConfirmationType { + return false + } + } + return true +} + +func vaultMethodsEqual(a, b []*uregistrytypes.VaultMethods) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i].Name != b[i].Name || + a[i].EventIdentifier != b[i].EventIdentifier || + a[i].ConfirmationType != b[i].ConfirmationType { + return false + } + } + return true +} + +func blockConfirmationEqual(a, b *uregistrytypes.BlockConfirmation) bool { + if a == nil && b == nil { + return true + } + if a == nil || b == nil { + return false + } + return a.FastInbound == b.FastInbound && + a.StandardInbound == b.StandardInbound } diff --git a/universalClient/chains/chains_test.go b/universalClient/chains/chains_test.go index c314d46c..d46c3336 100644 --- a/universalClient/chains/chains_test.go +++ b/universalClient/chains/chains_test.go @@ -163,52 +163,71 @@ func TestConfigsEqual(t *testing.T) { assert.False(t, configsEqual(cfg1, cfg2)) }) - t.Run("different enabled state returns false", func(t *testing.T) { + t.Run("different gateway methods returns false", func(t *testing.T) { cfg1 := &uregistrytypes.ChainConfig{ Chain: "chain1", - Enabled: &uregistrytypes.ChainEnabled{ - IsInboundEnabled: true, - IsOutboundEnabled: true, + GatewayMethods: []*uregistrytypes.GatewayMethods{ + {Name: "method1", Identifier: "0xabc"}, }, } cfg2 := &uregistrytypes.ChainConfig{ Chain: "chain1", - Enabled: &uregistrytypes.ChainEnabled{ - IsInboundEnabled: false, - IsOutboundEnabled: true, + GatewayMethods: []*uregistrytypes.GatewayMethods{ + {Name: "method1", Identifier: "0xdef"}, }, } assert.False(t, configsEqual(cfg1, cfg2)) }) - t.Run("both enabled nil returns true", func(t *testing.T) { + t.Run("different vault methods returns false", func(t *testing.T) { cfg1 := &uregistrytypes.ChainConfig{ - Chain: "chain1", - Enabled: nil, + Chain: "chain1", + VaultMethods: []*uregistrytypes.VaultMethods{ + {Name: "vault1", Identifier: "0xabc"}, + }, } cfg2 := &uregistrytypes.ChainConfig{ - Chain: "chain1", - Enabled: nil, + Chain: "chain1", + VaultMethods: nil, } - assert.True(t, configsEqual(cfg1, cfg2)) + assert.False(t, configsEqual(cfg1, cfg2)) }) - t.Run("one enabled nil returns false", func(t *testing.T) { + t.Run("different block confirmation returns false", func(t *testing.T) { cfg1 := &uregistrytypes.ChainConfig{ - Chain: "chain1", - Enabled: &uregistrytypes.ChainEnabled{ - IsInboundEnabled: true, - }, + Chain: "chain1", + BlockConfirmation: &uregistrytypes.BlockConfirmation{FastInbound: 2, StandardInbound: 12}, } cfg2 := &uregistrytypes.ChainConfig{ - Chain: "chain1", - Enabled: nil, + Chain: "chain1", + BlockConfirmation: &uregistrytypes.BlockConfirmation{FastInbound: 5, StandardInbound: 12}, } assert.False(t, configsEqual(cfg1, cfg2)) }) + + t.Run("same full config returns true", func(t *testing.T) { + cfg1 := &uregistrytypes.ChainConfig{ + Chain: "chain1", + GatewayAddress: "0x123", + GatewayMethods: []*uregistrytypes.GatewayMethods{ + {Name: "m1", Identifier: "0xabc"}, + }, + BlockConfirmation: &uregistrytypes.BlockConfirmation{FastInbound: 2, StandardInbound: 12}, + } + cfg2 := &uregistrytypes.ChainConfig{ + Chain: "chain1", + GatewayAddress: "0x123", + GatewayMethods: []*uregistrytypes.GatewayMethods{ + {Name: "m1", Identifier: "0xabc"}, + }, + BlockConfirmation: &uregistrytypes.BlockConfirmation{FastInbound: 2, StandardInbound: 12}, + } + + assert.True(t, configsEqual(cfg1, cfg2)) + }) } func TestChainAction(t *testing.T) { @@ -240,36 +259,10 @@ func TestDetermineChainAction(t *testing.T) { } chains := NewChains(nil, nil, cfg, logger) - t.Run("disabled chain returns skip", func(t *testing.T) { - chainCfg := &uregistrytypes.ChainConfig{ - Chain: "eip155:1", - Enabled: nil, - } - - action := chains.determineChainAction(chainCfg) - assert.Equal(t, chainActionSkip, action) - }) - - t.Run("disabled inbound and outbound returns skip", func(t *testing.T) { + t.Run("new chain returns add", func(t *testing.T) { chainCfg := &uregistrytypes.ChainConfig{ - Chain: "eip155:1", - Enabled: &uregistrytypes.ChainEnabled{ - IsInboundEnabled: false, - IsOutboundEnabled: false, - }, - } - - action := chains.determineChainAction(chainCfg) - assert.Equal(t, chainActionSkip, action) - }) - - t.Run("new enabled chain returns add", func(t *testing.T) { - chainCfg := &uregistrytypes.ChainConfig{ - Chain: "eip155:1", - Enabled: &uregistrytypes.ChainEnabled{ - IsInboundEnabled: true, - IsOutboundEnabled: false, - }, + Chain: "eip155:1", + VmType: uregistrytypes.VmType_EVM, } action := chains.determineChainAction(chainCfg) @@ -281,10 +274,6 @@ func TestDetermineChainAction(t *testing.T) { Chain: "eip155:1", VmType: uregistrytypes.VmType_EVM, GatewayAddress: "0x123", - Enabled: &uregistrytypes.ChainEnabled{ - IsInboundEnabled: true, - IsOutboundEnabled: true, - }, } // Add the chain first @@ -308,20 +297,12 @@ func TestDetermineChainAction(t *testing.T) { Chain: "eip155:1", VmType: uregistrytypes.VmType_EVM, GatewayAddress: "0x123", - Enabled: &uregistrytypes.ChainEnabled{ - IsInboundEnabled: true, - IsOutboundEnabled: true, - }, } newCfg := &uregistrytypes.ChainConfig{ Chain: "eip155:1", VmType: uregistrytypes.VmType_EVM, GatewayAddress: "0x456", // Different address - Enabled: &uregistrytypes.ChainEnabled{ - IsInboundEnabled: true, - IsOutboundEnabled: true, - }, } // Add the chain first From fb816ea36d3147f5421ab060827c5d282ef09f11 Mon Sep 17 00:00:00 2001 From: aman035 Date: Fri, 6 Mar 2026 18:11:58 +0530 Subject: [PATCH 6/6] add: identifier check --- universalClient/chains/chains.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/universalClient/chains/chains.go b/universalClient/chains/chains.go index fd74f257..5a0c4f01 100644 --- a/universalClient/chains/chains.go +++ b/universalClient/chains/chains.go @@ -515,6 +515,7 @@ func gatewayMethodsEqual(a, b []*uregistrytypes.GatewayMethods) bool { } for i := range a { if a[i].Name != b[i].Name || + a[i].Identifier != b[i].Identifier || a[i].EventIdentifier != b[i].EventIdentifier || a[i].ConfirmationType != b[i].ConfirmationType { return false @@ -529,6 +530,7 @@ func vaultMethodsEqual(a, b []*uregistrytypes.VaultMethods) bool { } for i := range a { if a[i].Name != b[i].Name || + a[i].Identifier != b[i].Identifier || a[i].EventIdentifier != b[i].EventIdentifier || a[i].ConfirmationType != b[i].ConfirmationType { return false