From 0f70cfd8b68db0d17c34df080096a154c56e15ba Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Wed, 22 Oct 2025 14:25:13 +0700 Subject: [PATCH] HitBTC: Fix panic in FetchTradablePairs (#2091) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` ❯ go test ./engine/... -run TestGetDefaultConfig --- FAIL: TestGetDefaultConfigurations (2.01s) --- FAIL: TestGetDefaultConfigurations/hitbtc (0.39s) panic: runtime error: slice bounds out of range [:-1] [recovered, repanicked] ``` The code was using strings.Index to find the quote currency position in the symbol ID, then slicing the ID. When Index returns -1 (not found), it caused a panic with 'slice bounds out of range [:-1]'. Fixed by using the BaseCurrency and QuoteCurrency fields directly from the Symbol struct, which is the correct approach and what the API provides. --- exchanges/hitbtc/hitbtc.go | 4 ++-- exchanges/hitbtc/hitbtc_test.go | 11 ++++++++--- exchanges/hitbtc/hitbtc_wrapper.go | 15 ++++++++------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/exchanges/hitbtc/hitbtc.go b/exchanges/hitbtc/hitbtc.go index bc1cc690..a382ddb1 100644 --- a/exchanges/hitbtc/hitbtc.go +++ b/exchanges/hitbtc/hitbtc.go @@ -103,8 +103,8 @@ func (e *Exchange) GetSymbols(ctx context.Context, symbol string) ([]string, err // GetSymbolsDetailed is the same as above but returns an array of symbols with // all their details. -func (e *Exchange) GetSymbolsDetailed(ctx context.Context) ([]Symbol, error) { - var resp []Symbol +func (e *Exchange) GetSymbolsDetailed(ctx context.Context) ([]*Symbol, error) { + var resp []*Symbol return resp, e.SendHTTPRequest(ctx, exchange.RestSpot, apiV2Symbol, &resp) } diff --git a/exchanges/hitbtc/hitbtc_test.go b/exchanges/hitbtc/hitbtc_test.go index 61485a0f..7f96c0b4 100644 --- a/exchanges/hitbtc/hitbtc_test.go +++ b/exchanges/hitbtc/hitbtc_test.go @@ -992,9 +992,14 @@ func TestGetOrderInfo(t *testing.T) { func TestFetchTradablePairs(t *testing.T) { t.Parallel() - if _, err := e.FetchTradablePairs(t.Context(), asset.Spot); err != nil { - t.Fatal(err) - } + + _, err := e.FetchTradablePairs(t.Context(), asset.Futures) + assert.ErrorIs(t, err, asset.ErrNotSupported) + + r, err := e.FetchTradablePairs(t.Context(), asset.Spot) + require.NoError(t, err) + require.NotEmpty(t, r) + assert.Contains(t, r, spotPair, "BTC-USD should be in the fetched pairs") } func TestGetCurrencyTradeURL(t *testing.T) { diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index 35d0c5ad..11405f2c 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -177,21 +177,22 @@ func (e *Exchange) Setup(exch *config.Exchange) error { } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (e *Exchange) FetchTradablePairs(ctx context.Context, _ asset.Item) (currency.Pairs, error) { +func (e *Exchange) FetchTradablePairs(ctx context.Context, a asset.Item) (currency.Pairs, error) { + if a != asset.Spot { + return nil, fmt.Errorf("%w: %q", asset.ErrNotSupported, a) + } + symbols, err := e.GetSymbolsDetailed(ctx) if err != nil { return nil, err } pairs := make([]currency.Pair, len(symbols)) - for x := range symbols { - index := strings.Index(symbols[x].ID, symbols[x].QuoteCurrency) - var pair currency.Pair - pair, err = currency.NewPairFromStrings(symbols[x].ID[:index], symbols[x].ID[index:]) - if err != nil { + for i, s := range symbols { + // s.QuoteCurrency is actually settlement currency, so trim the base currency to get the real quote currency + if pairs[i], err = currency.NewPairFromStrings(s.BaseCurrency, strings.TrimPrefix(s.ID, s.BaseCurrency)); err != nil { return nil, err } - pairs[x] = pair } return pairs, nil }