currency: Fix edge case in NewCode where all string characters are digits (#1593)

* add fixes and test

* glorious: nits, privatise upper case field, add item field for case sensitivity for format checks, rm dead code.

* fix potential panic

* gk/glorious: nits

* gk: nits and other things

* improve commentary lol

* glorious+gk: nits and improvements (with no sillyness this time)

* Update currency/pairs.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* thrasher: nitssssss

* linter: fix

* bye bye silly boy

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
This commit is contained in:
Ryan O'Hara-Reid
2024-11-25 14:51:35 +11:00
committed by GitHub
parent 1fab9c72d2
commit 4bbaf75d34
13 changed files with 267 additions and 288 deletions

View File

@@ -186,14 +186,7 @@ func (b *BaseCodes) Register(c string, newRole Role) Code {
return EMPTYCODE
}
var format bool
// Digits fool upper and lower casing. So find first letter and check case.
for x := range c {
if unicode.IsLetter(rune(c[x])) {
format = unicode.IsUpper(rune(c[x]))
break
}
}
isUpperCase := strings.ContainsFunc(c, func(r rune) bool { return unicode.IsLetter(r) && unicode.IsUpper(r) })
// Force upper string storage and matching
c = strings.ToUpper(c)
@@ -218,13 +211,13 @@ func (b *BaseCodes) Register(c string, newRole Role) Code {
}
stored[x].Role = newRole
}
return Code{Item: stored[x], UpperCase: format}
return Code{Item: stored[x], upperCase: isUpperCase}
}
}
newItem := &Item{Symbol: c, Lower: strings.ToLower(c), Role: newRole}
b.Items[c] = append(b.Items[c], newItem)
return Code{Item: newItem, UpperCase: format}
return Code{Item: newItem, upperCase: isUpperCase}
}
// LoadItem sets item data
@@ -275,21 +268,29 @@ func (c Code) String() string {
if c.Item == nil {
return ""
}
if c.UpperCase {
if c.upperCase {
return c.Item.Symbol
}
return c.Item.Lower
}
// Lower converts the code to lowercase formatting
// Lower flags the Code to use LowerCase formatting, but does not change Symbol
// If Code cannot be lowercased then it will return Code unchanged
func (c Code) Lower() Code {
c.UpperCase = false
if c.Item == nil {
return c
}
c.upperCase = false
return c
}
// Upper converts the code to uppercase formatting
// Upper flags the Code to use UpperCase formatting, but does not change Symbol
// If Code cannot be uppercased then it will return Code unchanged
func (c Code) Upper() Code {
c.UpperCase = true
if c.Item == nil {
return c
}
c.upperCase = true
return c
}
@@ -346,21 +347,3 @@ func (i *Item) Currency() Code {
}
return NewCode(i.Symbol)
}
// UpperCurrency allows an item to revert to a code
// taking an upper
func (i *Item) UpperCurrency() Code {
if i == nil {
return EMPTYCODE.Upper()
}
return NewCode(i.Symbol).Upper()
}
// LowerCurrency allows an item to revert to a code
// returning in lower format
func (i *Item) LowerCurrency() Code {
if i == nil {
return EMPTYCODE.Lower()
}
return NewCode(i.Symbol).Lower()
}

View File

@@ -4,6 +4,8 @@ import (
"encoding/json"
"errors"
"testing"
"github.com/stretchr/testify/require"
)
func TestRoleString(t *testing.T) {
@@ -456,6 +458,16 @@ func TestBaseCode(t *testing.T) {
}
}
func TestNewCodeFormatting(t *testing.T) {
require.True(t, NewCode("BTC").upperCase)
require.False(t, NewCode("btc").upperCase)
require.True(t, NewCode("BTC").Equal(NewCode("btc")))
require.False(t, NewCode("420").upperCase)
require.False(t, NewCode("btc420").upperCase)
require.False(t, NewCode("420").Lower().upperCase)
require.True(t, NewCode("4BTC").upperCase)
}
func TestCodeString(t *testing.T) {
if cc, expected := NewCode("TEST"), "TEST"; cc.String() != expected {
t.Errorf("Currency Code String() error expected %s but received %s",

View File

@@ -40,7 +40,7 @@ type Code struct {
// TODO: Below will force the use of the Equal method for comparison. Big
// job to update all maps and instances through the code base.
// _ []struct{}
UpperCase bool
upperCase bool
}
// Item defines a sub type containing the main attributes of a designated

View File

@@ -497,7 +497,7 @@ func (p *PairsManager) SetDelimitersFromConfig() error {
}
for i, p := range []Pairs{s.Enabled, s.Available} {
for j := range p {
if p[j].Delimiter == cf.Delimiter {
if cf.Delimiter == "" || p[j].Delimiter == cf.Delimiter {
continue
}
nP, err := NewPairDelimiter(p[j].String(), cf.Delimiter)

View File

@@ -842,7 +842,7 @@ func TestPairManagerSetDelimitersFromConfig(t *testing.T) {
err = json.Unmarshal([]byte(`{"pairs":{"spot":{"configFormat":{"delimiter":"_"},"enabled":"BTC-USDT","available":"BTC-USDT"}}}`), p)
if assert.NoError(t, err, "UnmarshalJSON should not error") {
err := p.SetDelimitersFromConfig()
assert.ErrorContains(t, err, "spot.enabled.BTC-USDT: delimiter: [_] not found in currencypair string", "SetDelimitersFromConfig should error correctly")
assert.ErrorIs(t, err, errDelimiterNotFound, "SetDelimitersFromConfig should error correctly")
}
}

View File

@@ -7,7 +7,11 @@ import (
"unicode"
)
var errCannotCreatePair = errors.New("cannot create currency pair")
var (
errCannotCreatePair = errors.New("cannot create currency pair")
errDelimiterNotFound = errors.New("delimiter not found")
errDelimiterCannotBeEmpty = errors.New("delimiter cannot be empty")
)
// NewBTCUSDT is a shortcut for NewPair(BTC, USDT)
func NewBTCUSDT() Pair {
@@ -21,25 +25,18 @@ func NewBTCUSD() Pair {
// NewPairDelimiter splits the desired currency string at delimiter, then returns a Pair struct
func NewPairDelimiter(currencyPair, delimiter string) (Pair, error) {
if !strings.Contains(currencyPair, delimiter) {
if currencyPair == "" {
return EMPTYPAIR, errEmptyPairString
}
if delimiter == "" {
return EMPTYPAIR, errDelimiterCannotBeEmpty
}
index := strings.Index(currencyPair, delimiter)
if index == -1 {
return EMPTYPAIR,
fmt.Errorf("delimiter: [%s] not found in currencypair string", delimiter)
fmt.Errorf("supplied pair: [%s] %s %w", currencyPair, delimiter, errDelimiterNotFound)
}
result := strings.Split(currencyPair, delimiter)
if len(result) < 2 {
return EMPTYPAIR,
fmt.Errorf("supplied pair: [%s] cannot be split with %s",
currencyPair,
delimiter)
}
if len(result) > 2 {
result[1] = strings.Join(result[1:], delimiter)
}
return Pair{
Delimiter: delimiter,
Base: NewCode(result[0]),
Quote: NewCode(result[1]),
}, nil
return Pair{Delimiter: delimiter, Base: NewCode(currencyPair[:index]), Quote: NewCode(currencyPair[index+1:])}, nil
}
// NewPairFromStrings returns a CurrencyPair without a delimiter
@@ -61,19 +58,12 @@ func NewPairFromStrings(base, quote string) (Pair, error) {
// NewPair returns a currency pair from currency codes
func NewPair(baseCurrency, quoteCurrency Code) Pair {
return Pair{
Base: baseCurrency,
Quote: quoteCurrency,
}
return Pair{Base: baseCurrency, Quote: quoteCurrency}
}
// NewPairWithDelimiter returns a CurrencyPair with a delimiter
func NewPairWithDelimiter(base, quote, delimiter string) Pair {
return Pair{
Base: NewCode(base),
Quote: NewCode(quote),
Delimiter: delimiter,
}
return Pair{Base: NewCode(base), Quote: NewCode(quote), Delimiter: delimiter}
}
// NewPairFromString converts currency string into a new CurrencyPair
@@ -150,11 +140,21 @@ func MatchPairsWithNoDelimiter(currencyPair string, pairs Pairs, pairFmt PairFor
// GetFormatting returns the formatting style of a pair
func (p Pair) GetFormatting() (PairFormat, error) {
if p.Base.UpperCase != p.Quote.UpperCase {
return PairFormat{}, fmt.Errorf("%w casing mismatch", errPairFormattingInconsistent)
if p.Base.isCaseSensitive() && p.Quote.isCaseSensitive() && (p.Base.upperCase != p.Quote.upperCase) {
return EMPTYFORMAT, fmt.Errorf("%w casing mismatch", errPairFormattingInconsistent)
}
return PairFormat{
Uppercase: p.Base.UpperCase,
Delimiter: p.Delimiter,
}, nil
return PairFormat{Uppercase: p.Base.upperCase || p.Quote.upperCase, Delimiter: p.Delimiter}, nil
}
func (p Pair) hasFormatDifference(pairFmt PairFormat) bool {
return p.Delimiter != pairFmt.Delimiter ||
(p.Base.isCaseSensitive() && p.Base.upperCase != pairFmt.Uppercase) ||
(p.Quote.isCaseSensitive() && p.Quote.upperCase != pairFmt.Uppercase)
}
func (c Code) isCaseSensitive() bool {
if c.Item == nil {
return false
}
return c.Item.Symbol != c.Item.Lower
}

View File

@@ -349,66 +349,29 @@ func TestNewPairWithDelimiter(t *testing.T) {
func TestNewPairDelimiter(t *testing.T) {
t.Parallel()
_, err := NewPairDelimiter("", "")
if err == nil {
t.Fatal("error cannot be nil")
}
require.ErrorIs(t, err, errEmptyPairString)
_, err = NewPairDelimiter("BTC_USD", "")
require.ErrorIs(t, err, errDelimiterCannotBeEmpty)
_, err = NewPairDelimiter("BTC_USD", "wow")
if err == nil {
t.Fatal("error cannot be nil")
}
require.ErrorIs(t, err, errDelimiterNotFound)
_, err = NewPairDelimiter("BTC_USD", " ")
if err == nil {
t.Fatal("error cannot be nil")
}
require.ErrorIs(t, err, errDelimiterNotFound)
pair, err := NewPairDelimiter(defaultPairWDelimiter, "-")
if err != nil {
t.Fatal(err)
}
actual := pair.String()
expected := defaultPairWDelimiter
if actual != expected {
t.Errorf(
"Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
actual = pair.Delimiter
expected = "-"
if actual != expected {
t.Errorf(
"Delmiter: %s was not equal to expected value: %s",
actual, expected,
)
}
require.NoError(t, err)
assert.Equal(t, defaultPairWDelimiter, pair.String())
assert.Equal(t, "-", pair.Delimiter)
pair, err = NewPairDelimiter("BTC-MOVE-0626", "-")
if err != nil {
t.Fatal(err)
}
actual = pair.String()
expected = "BTC-MOVE-0626"
if actual != expected {
t.Errorf(
"Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
require.NoError(t, err)
assert.Equal(t, "BTC-MOVE-0626", pair.String())
pair, err = NewPairDelimiter("fBTC-USDT", "-")
if err != nil {
t.Fatal(err)
}
actual = pair.String()
expected = "fbtc-USDT"
if actual != expected {
t.Errorf(
"Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
pair, err = NewPairDelimiter("sETH-USDT", "-")
require.NoError(t, err)
assert.Equal(t, "SETH-USDT", pair.String(), "If any upper case is found in set this forces the pair to be uppercase")
}
func TestNewPairFromString(t *testing.T) {
@@ -564,89 +527,6 @@ func TestCopyPairFormat(t *testing.T) {
}
}
func TestFindPairDifferences(t *testing.T) {
pairList, err := NewPairsFromStrings([]string{defaultPairWDelimiter, "ETH-USD", "LTC-USD"})
if err != nil {
t.Fatal(err)
}
dash, err := NewPairsFromStrings([]string{"DASH-USD"})
if err != nil {
t.Fatal(err)
}
// Test new pair update
diff, err := pairList.FindDifferences(dash, PairFormat{Delimiter: DashDelimiter, Uppercase: true})
if err != nil {
t.Fatal(err)
}
if len(diff.New) != 1 && len(diff.Remove) != 3 && diff.FormatDifference {
t.Error("TestFindPairDifferences: Unexpected values")
}
diff, err = pairList.FindDifferences(Pairs{}, EMPTYFORMAT)
if err != nil {
t.Fatal(err)
}
if len(diff.New) != 0 && len(diff.Remove) != 3 && !diff.FormatDifference {
t.Error("TestFindPairDifferences: Unexpected values")
}
diff, err = Pairs{}.FindDifferences(pairList, EMPTYFORMAT)
if err != nil {
t.Fatal(err)
}
if len(diff.New) != 3 && len(diff.Remove) != 0 && diff.FormatDifference {
t.Error("TestFindPairDifferences: Unexpected values")
}
// Test that the supplied pair lists are the same, so
// no newPairs or removedPairs
diff, err = pairList.FindDifferences(pairList, PairFormat{Delimiter: DashDelimiter, Uppercase: true})
if err != nil {
t.Fatal(err)
}
if len(diff.New) != 0 && len(diff.Remove) != 0 && !diff.FormatDifference {
t.Error("TestFindPairDifferences: Unexpected values")
}
_, err = pairList.FindDifferences(Pairs{EMPTYPAIR}, EMPTYFORMAT)
if !errors.Is(err, ErrCurrencyPairEmpty) {
t.Fatalf("received: '%v' but expected: '%v'", err, ErrCurrencyPairEmpty)
}
_, err = Pairs{EMPTYPAIR}.FindDifferences(pairList, EMPTYFORMAT)
if !errors.Is(err, ErrCurrencyPairEmpty) {
t.Fatalf("received: '%v' but expected: '%v'", err, ErrCurrencyPairEmpty)
}
// Test duplication
duplication, err := NewPairsFromStrings([]string{defaultPairWDelimiter, "ETH-USD", "LTC-USD", "ETH-USD"})
if err != nil {
t.Fatal(err)
}
_, err = pairList.FindDifferences(duplication, EMPTYFORMAT)
if !errors.Is(err, ErrPairDuplication) {
t.Fatalf("received: '%v' but expected: '%v'", err, ErrPairDuplication)
}
// This will allow for the removal of the duplicated item to be returned if
// contained in the original list.
diff, err = duplication.FindDifferences(pairList, EMPTYFORMAT)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if len(diff.Remove) != 1 {
t.Fatal("expected removal value in pair difference struct")
}
if !diff.Remove[0].Equal(pairList[1]) {
t.Fatal("unexpected value returned", diff.Remove[0], pairList[1])
}
}
func TestPairsToStringArray(t *testing.T) {
var pairs Pairs
pairs = append(pairs, NewPair(BTC, USD))
@@ -987,29 +867,38 @@ func TestIsAssociated(t *testing.T) {
func TestPair_GetFormatting(t *testing.T) {
t.Parallel()
p := NewPair(BTC, USDT)
pFmt, err := p.GetFormatting()
if err != nil {
t.Error(err)
}
if !pFmt.Uppercase || pFmt.Delimiter != "" {
t.Error("incorrect formatting")
}
pFmt, err := NewPair(BTC, USDT).GetFormatting()
require.NoError(t, err)
assert.True(t, pFmt.Uppercase)
assert.Empty(t, pFmt.Delimiter)
p = NewPairWithDelimiter("eth", "usdt", "/")
pFmt, err = p.GetFormatting()
if err != nil {
t.Error(err)
}
if pFmt.Uppercase || pFmt.Delimiter != "/" {
t.Error("incorrect formatting")
}
pFmt, err = NewPairWithDelimiter("eth", "usdt", "/").GetFormatting()
require.NoError(t, err)
assert.False(t, pFmt.Uppercase)
assert.Equal(t, "/", pFmt.Delimiter)
p = NewPairWithDelimiter("eth", "USDT", "/")
_, err = p.GetFormatting()
if !errors.Is(err, errPairFormattingInconsistent) {
t.Error(err)
}
_, err = NewPairWithDelimiter("eth", "USDT", "/").GetFormatting()
require.ErrorIs(t, err, errPairFormattingInconsistent)
pFmt, err = EMPTYPAIR.GetFormatting()
require.NoError(t, err)
assert.Equal(t, EMPTYFORMAT, pFmt)
pFmt, err = NewPairWithDelimiter("eth", "420", "/").GetFormatting()
require.NoError(t, err)
assert.False(t, pFmt.Uppercase)
pFmt, err = NewPairWithDelimiter("ETH", "420", "/").GetFormatting()
require.NoError(t, err)
assert.True(t, pFmt.Uppercase)
pFmt, err = NewPairWithDelimiter("420", "eth", "/").GetFormatting()
require.NoError(t, err)
assert.False(t, pFmt.Uppercase)
pFmt, err = NewPairWithDelimiter("420", "ETH", "/").GetFormatting()
require.NoError(t, err)
assert.True(t, pFmt.Uppercase)
}
func TestNewBTCUSD(t *testing.T) {

View File

@@ -60,8 +60,8 @@ func (p Pairs) Join() string {
func (p Pairs) Format(pairFmt PairFormat) Pairs {
pairs := slices.Clone(p)
for x := range pairs {
pairs[x].Base.UpperCase = pairFmt.Uppercase
pairs[x].Quote.UpperCase = pairFmt.Uppercase
pairs[x].Base.upperCase = pairFmt.Uppercase
pairs[x].Quote.upperCase = pairFmt.Uppercase
pairs[x].Delimiter = pairFmt.Delimiter
}
return pairs
@@ -233,52 +233,57 @@ func (p Pairs) GetMatch(pair Pair) (Pair, error) {
return EMPTYPAIR, ErrPairNotFound
}
type pairKey struct {
Base *Item
Quote *Item
}
// FindDifferences returns pairs which are new or have been removed
func (p Pairs) FindDifferences(incoming Pairs, pairFmt PairFormat) (PairDifference, error) {
newPairs := make(Pairs, 0, len(incoming))
check := make(map[string]bool)
check := make(map[pairKey]bool)
formatDiff := false
for x := range incoming {
if incoming[x].IsEmpty() {
return PairDifference{}, fmt.Errorf("contained in the incoming pairs a %w", ErrCurrencyPairEmpty)
}
format := EMPTYFORMAT.Format(incoming[x])
if check[format] {
if !formatDiff {
formatDiff = incoming[x].hasFormatDifference(pairFmt)
}
k := pairKey{Base: incoming[x].Base.Item, Quote: incoming[x].Quote.Item}
if check[k] {
return PairDifference{}, fmt.Errorf("contained in the incoming pairs %w", ErrPairDuplication)
}
check[format] = true
check[k] = true
if !p.Contains(incoming[x], true) {
newPairs = append(newPairs, incoming[x])
}
}
removedPairs := make(Pairs, 0, len(p))
check = make(map[string]bool)
clear(check)
for x := range p {
if p[x].IsEmpty() {
return PairDifference{}, fmt.Errorf("contained in the existing pairs a %w", ErrCurrencyPairEmpty)
}
format := EMPTYFORMAT.Format(p[x])
if !incoming.Contains(p[x], true) || check[format] {
if !formatDiff {
formatDiff = p[x].hasFormatDifference(pairFmt)
}
k := pairKey{Base: p[x].Base.Item, Quote: p[x].Quote.Item}
if !incoming.Contains(p[x], true) || check[k] {
removedPairs = append(removedPairs, p[x])
}
check[format] = true
check[k] = true
}
return PairDifference{
New: newPairs,
Remove: removedPairs,
FormatDifference: p.HasFormatDifference(pairFmt),
}, nil
return PairDifference{New: newPairs, Remove: removedPairs, FormatDifference: formatDiff}, nil
}
// HasFormatDifference checks and validates full formatting across a pairs list
func (p Pairs) HasFormatDifference(pairFmt PairFormat) bool {
for x := range p {
if p[x].Delimiter != pairFmt.Delimiter ||
(!p[x].Base.IsEmpty() && p[x].Base.UpperCase != pairFmt.Uppercase) ||
(!p[x].Quote.IsEmpty() && p[x].Quote.UpperCase != pairFmt.Uppercase) {
return true
}
}
return false
return slices.ContainsFunc(p, func(pair Pair) bool { return pair.hasFormatDifference(pairFmt) })
}
// GetRandomPair returns a random pair from a list of pairs
@@ -328,10 +333,10 @@ func (p Pairs) GetCrypto() Currencies {
m := make(map[*Item]bool)
for x := range p {
if p[x].Base.IsCryptocurrency() {
m[p[x].Base.Item] = p[x].Base.UpperCase
m[p[x].Base.Item] = p[x].Base.upperCase
}
if p[x].Quote.IsCryptocurrency() {
m[p[x].Quote.Item] = p[x].Quote.UpperCase
m[p[x].Quote.Item] = p[x].Quote.upperCase
}
}
return currencyConstructor(m)
@@ -342,10 +347,10 @@ func (p Pairs) GetFiat() Currencies {
m := make(map[*Item]bool)
for x := range p {
if p[x].Base.IsFiatCurrency() {
m[p[x].Base.Item] = p[x].Base.UpperCase
m[p[x].Base.Item] = p[x].Base.upperCase
}
if p[x].Quote.IsFiatCurrency() {
m[p[x].Quote.Item] = p[x].Quote.UpperCase
m[p[x].Quote.Item] = p[x].Quote.upperCase
}
}
return currencyConstructor(m)
@@ -356,8 +361,8 @@ func (p Pairs) GetFiat() Currencies {
func (p Pairs) GetCurrencies() Currencies {
m := make(map[*Item]bool)
for x := range p {
m[p[x].Base.Item] = p[x].Base.UpperCase
m[p[x].Quote.Item] = p[x].Quote.UpperCase
m[p[x].Base.Item] = p[x].Base.upperCase
m[p[x].Quote.Item] = p[x].Quote.upperCase
}
return currencyConstructor(m)
}
@@ -367,10 +372,10 @@ func (p Pairs) GetStables() Currencies {
m := make(map[*Item]bool)
for x := range p {
if p[x].Base.IsStableCurrency() {
m[p[x].Base.Item] = p[x].Base.UpperCase
m[p[x].Base.Item] = p[x].Base.upperCase
}
if p[x].Quote.IsStableCurrency() {
m[p[x].Quote.Item] = p[x].Quote.UpperCase
m[p[x].Quote.Item] = p[x].Quote.upperCase
}
}
return currencyConstructor(m)
@@ -383,7 +388,7 @@ func currencyConstructor(m map[*Item]bool) Currencies {
var target int
for code, upper := range m {
cryptos[target].Item = code
cryptos[target].UpperCase = upper
cryptos[target].upperCase = upper
target++
}
return cryptos

View File

@@ -715,35 +715,40 @@ func TestValidateAndConform(t *testing.T) {
func TestPairs_GetFormatting(t *testing.T) {
t.Parallel()
p := Pairs{NewPair(BTC, USDT)}
pFmt, err := p.GetFormatting()
if err != nil {
t.Error(err)
}
if !pFmt.Uppercase || pFmt.Delimiter != "" {
t.Error("incorrect formatting")
}
pFmt, err := Pairs{NewPair(BTC, USDT)}.GetFormatting()
require.NoError(t, err)
assert.True(t, pFmt.Uppercase)
assert.Empty(t, pFmt.Delimiter)
p = Pairs{NewPairWithDelimiter("eth", "usdt", "/")}
pFmt, err = p.GetFormatting()
if err != nil {
t.Error(err)
}
if pFmt.Uppercase || pFmt.Delimiter != "/" {
t.Error("incorrect formatting")
}
pFmt, err = Pairs{NewPairWithDelimiter("eth", "usdt", "/")}.GetFormatting()
require.NoError(t, err)
assert.False(t, pFmt.Uppercase)
assert.Equal(t, "/", pFmt.Delimiter)
p = Pairs{NewPair(BTC, USDT), NewPairWithDelimiter("eth", "usdt", "/")}
_, err = p.GetFormatting()
if !errors.Is(err, errPairFormattingInconsistent) {
t.Error(err)
}
_, err = Pairs{NewPair(BTC, USDT), NewPairWithDelimiter("eth", "usdt", "/")}.GetFormatting()
require.ErrorIs(t, err, errPairFormattingInconsistent)
p = Pairs{NewPairWithDelimiter("eth", "USDT", "/")}
_, err = p.GetFormatting()
if !errors.Is(err, errPairFormattingInconsistent) {
t.Error(err)
}
_, err = Pairs{NewPairWithDelimiter("eth", "USDT", "/")}.GetFormatting()
require.ErrorIs(t, err, errPairFormattingInconsistent)
_, err = Pairs{NewPairWithDelimiter("eth", "usdt", "/"), NewPairWithDelimiter("eth", "usdt", "/")}.GetFormatting()
require.NoError(t, err)
_, err = Pairs{NewPairWithDelimiter("eth", "usdt", "/"), NewPairWithDelimiter("eth", "usdt", "|")}.GetFormatting()
require.ErrorIs(t, err, errPairFormattingInconsistent)
_, err = Pairs{NewPairWithDelimiter("eth", "420", "/"), NewPairWithDelimiter("eth", "420", "/")}.GetFormatting()
require.NoError(t, err)
_, err = Pairs{NewPairWithDelimiter("ETH", "420", "/"), NewPairWithDelimiter("ETH", "420", "/")}.GetFormatting()
require.NoError(t, err)
_, err = Pairs{NewPairWithDelimiter("420", "ETH", "/"), NewPairWithDelimiter("420", "ETH", "/")}.GetFormatting()
require.NoError(t, err)
_, err = Pairs{NewPairWithDelimiter("420", "eth", "/"), NewPairWithDelimiter("420", "eth", "/")}.GetFormatting()
require.NoError(t, err)
_, err = Pairs{NewPairWithDelimiter("420", "eth", "/"), NewPairWithDelimiter("eth", "420", "/")}.GetFormatting()
require.NoError(t, err)
_, err = Pairs{NewPairWithDelimiter("420", "ETH", "/"), NewPairWithDelimiter("ETH", "420", "/")}.GetFormatting()
require.NoError(t, err)
}
func TestGetPairsByQuote(t *testing.T) {
@@ -836,3 +841,83 @@ func TestPairsEqual(t *testing.T) {
assert.Equal(t, "USDT-BTC", orig[0].String(), "Equal Pairs should not effect original order or format")
assert.False(t, orig.Equal(Pairs{NewPair(DAI, XRP), NewPair(DAI, BTC), NewPair(USD, LTC)}), "UnEqual Pairs should return false")
}
func TestFindPairDifferences(t *testing.T) {
pairList, err := NewPairsFromStrings([]string{defaultPairWDelimiter, "ETH-USD", "LTC-USD"})
require.NoError(t, err)
dash, err := NewPairsFromStrings([]string{"DASH-USD"})
require.NoError(t, err)
// Test new pair update
diff, err := pairList.FindDifferences(dash, PairFormat{Delimiter: DashDelimiter, Uppercase: true})
require.NoError(t, err)
assert.Len(t, diff.New, 1)
assert.Len(t, diff.Remove, 3)
diff, err = pairList.FindDifferences(Pairs{}, EMPTYFORMAT)
require.NoError(t, err)
assert.Empty(t, diff.New)
assert.Len(t, diff.Remove, 3)
assert.True(t, diff.FormatDifference)
diff, err = Pairs{}.FindDifferences(pairList, EMPTYFORMAT)
require.NoError(t, err)
assert.Len(t, diff.New, 3)
assert.Empty(t, diff.Remove)
assert.True(t, diff.FormatDifference)
// Test that the supplied pair lists are the same, so
// no newPairs or removedPairs
diff, err = pairList.FindDifferences(pairList, PairFormat{Delimiter: DashDelimiter, Uppercase: true})
require.NoError(t, err)
assert.Empty(t, diff.New)
assert.Empty(t, diff.Remove)
assert.False(t, diff.FormatDifference)
_, err = pairList.FindDifferences(Pairs{EMPTYPAIR}, EMPTYFORMAT)
require.ErrorIs(t, err, ErrCurrencyPairEmpty)
_, err = Pairs{EMPTYPAIR}.FindDifferences(pairList, EMPTYFORMAT)
require.ErrorIs(t, err, ErrCurrencyPairEmpty)
// Test duplication
duplication, err := NewPairsFromStrings([]string{defaultPairWDelimiter, "ETH-USD", "LTC-USD", "ETH-USD"})
require.NoError(t, err)
_, err = pairList.FindDifferences(duplication, EMPTYFORMAT)
require.ErrorIs(t, err, ErrPairDuplication)
// This will allow for the removal of the duplicated item to be returned if
// contained in the original list.
diff, err = duplication.FindDifferences(pairList, EMPTYFORMAT)
require.NoError(t, err)
require.Len(t, diff.Remove, 1)
require.True(t, diff.Remove[0].Equal(pairList[1]))
original, err := NewPairsFromStrings([]string{"ETH-USD", "LTC-USD", "ETH-USD"})
require.NoError(t, err)
compare, err := NewPairsFromStrings([]string{"ETH-123", "LTC-123", "MEOW-123"})
require.NoError(t, err)
diff, err = original.FindDifferences(compare, PairFormat{Delimiter: DashDelimiter, Uppercase: true})
require.NoError(t, err)
require.False(t, diff.FormatDifference)
}
// 2208139 509.3 ns/op 288 B/op 2 allocs/op (current)
//
// 1614865 712.5 ns/op 336 B/op 8 allocs/op (prev)
func BenchmarkFindDifferences(b *testing.B) {
original, err := NewPairsFromStrings([]string{"ETH-USD", "LTC-USD", "ETH-USD"})
require.NoError(b, err)
compare, err := NewPairsFromStrings([]string{"ETH-123", "LTC-123", "MEOW-123"})
require.NoError(b, err)
for i := 0; i < b.N; i++ {
_, err = original.FindDifferences(compare, EMPTYFORMAT)
require.NoError(b, err)
}
}

View File

@@ -55,7 +55,7 @@ func (s *Storage) SetDefaults() {
if item == USDT.Item {
continue
}
fiatCurrencies = append(fiatCurrencies, Code{Item: item, UpperCase: true})
fiatCurrencies = append(fiatCurrencies, Code{Item: item, upperCase: true})
}
err := s.SetDefaultFiatCurrencies(fiatCurrencies)

View File

@@ -112,7 +112,7 @@ func GetHoldings(exch string, creds *Credentials, assetType asset.Item) (Holding
}
assetHoldings.m.Lock()
currencyBalances = append(currencyBalances, Balance{
Currency: currency.Code{Item: mapKey.Currency, UpperCase: true},
Currency: mapKey.Currency.Currency().Upper(),
Total: assetHoldings.total,
Hold: assetHoldings.hold,
Free: assetHoldings.free,

View File

@@ -1177,9 +1177,14 @@ func (b *Bitfinex) GetHistoricCandlesExtended(ctx context.Context, pair currency
}
func (b *Bitfinex) fixCasing(in currency.Pair, a asset.Item) (string, error) {
if in.IsEmpty() || in.Base.IsEmpty() {
if in.Base.IsEmpty() {
return "", currency.ErrCurrencyPairEmpty
}
// Convert input to lowercase to ensure consistent formatting.
// Required for currencies that start with T or F eg tTNBUSD
in = in.Lower()
var checkString [2]byte
if a == asset.Spot || a == asset.Margin {
checkString[0] = 't'

View File

@@ -96,10 +96,10 @@ var (
Value: "error",
}
currencyPair = &objects.String{
Value: "BTCUSD",
Value: "BTC-USD",
}
delimiter = &objects.String{
Value: "",
Value: "-",
}
assetType = &objects.String{
Value: "spot",