@@ -6,8 +6,10 @@ import (
6
6
)
7
7
8
8
const (
9
- ethereum = "ETHEREUM"
10
- MaxCPFieldLength = 128
9
+ ethereum = "ETHEREUM"
10
+ MaxCPFieldLength = 256
11
+ fieldSeparator = ","
12
+ expectedSplitLength = 3
11
13
)
12
14
13
15
// NewCurrencyPair returns a new CurrencyPair with the given base and quote strings.
@@ -18,19 +20,135 @@ func NewCurrencyPair(base, quote string) CurrencyPair {
18
20
}
19
21
}
20
22
23
+ // IsLegacyAssetString returns true if the asset string is of the following format:
24
+ // - contains no instances of fieldSeparator.
25
+ func IsLegacyAssetString (asset string ) bool {
26
+ return ! strings .Contains (asset , fieldSeparator )
27
+ }
28
+
29
+ // coreValidate performs checks that are universal across any ticker format style, namely:
30
+ // - check that base and quote are not empty.
31
+ // - check that the length of the base and quote fields do not exceed MaxCPFieldLength.
32
+ func (cp * CurrencyPair ) coreValidate () error {
33
+ if cp .Base == "" {
34
+ return fmt .Errorf ("base asset cannot be empty" )
35
+ }
36
+
37
+ if cp .Quote == "" {
38
+ return fmt .Errorf ("quote asset cannot be empty" )
39
+ }
40
+
41
+ if len (cp .Base ) > MaxCPFieldLength {
42
+ return fmt .Errorf ("base asset exceeds max length of %d" , MaxCPFieldLength )
43
+ }
44
+
45
+ if len (cp .Quote ) > MaxCPFieldLength {
46
+ return fmt .Errorf ("quote asset exceeds max length of %d" , MaxCPFieldLength )
47
+ }
48
+
49
+ return nil
50
+ }
51
+
21
52
// ValidateBasic checks that the Base / Quote strings in the CurrencyPair are formatted correctly, i.e.
22
- // Base + Quote are non-empty, and are in upper-case.
53
+ // For base and quote asset:
54
+ // check if the asset is formatted in the legacy validation form:
55
+ // - if so, check that fields are not empty and all upper case
56
+ // - else, check that the format is in the following form: tokenName,tokenAddress,chainID
23
57
func (cp * CurrencyPair ) ValidateBasic () error {
58
+ if err := cp .coreValidate (); err != nil {
59
+ return err
60
+ }
61
+
62
+ if IsLegacyAssetString (cp .Base ) {
63
+ err := ValidateLegacyAssetString (cp .Base )
64
+ if err != nil {
65
+ return fmt .Errorf ("base asset %q is invalid: %w" , cp .Base , err )
66
+ }
67
+ } else {
68
+ err := ValidateDefiAssetString (cp .Base )
69
+ if err != nil {
70
+ return fmt .Errorf ("base defi asset %q is invalid: %w" , cp .Base , err )
71
+ }
72
+ }
73
+
74
+ // check quote asset
75
+ if IsLegacyAssetString (cp .Quote ) {
76
+ err := ValidateLegacyAssetString (cp .Quote )
77
+ if err != nil {
78
+ return fmt .Errorf ("quote asset %q is invalid: %w" , cp .Quote , err )
79
+ }
80
+ } else {
81
+ err := ValidateDefiAssetString (cp .Quote )
82
+ if err != nil {
83
+ return fmt .Errorf ("quote defi asset %q is invalid: %w" , cp .Quote , err )
84
+ }
85
+ }
86
+
87
+ return nil
88
+ }
89
+
90
+ // ValidateLegacyAssetString checks if the asset string is formatted correctly, i.e.
91
+ // - asset string is fully uppercase
92
+ // - asset string does not contain the `fieldSeparator`
93
+ //
94
+ // NOTE: this function assumes that coreValidate() has already been run.
95
+ func ValidateLegacyAssetString (asset string ) error {
96
+ // check formatting of asset
97
+ if strings .ToUpper (asset ) != asset {
98
+ return fmt .Errorf ("incorrectly formatted asset string, expected: %q got: %q" , strings .ToUpper (asset ), asset )
99
+ }
100
+
101
+ if ! IsLegacyAssetString (asset ) {
102
+ return fmt .Errorf ("incorrectly formatted asset string, asset %q should not contain the %q character" , asset ,
103
+ fieldSeparator )
104
+ }
105
+
106
+ return nil
107
+ }
108
+
109
+ // ValidateDefiAssetString checks that the asset string is formatted properly as a defi asset (tokenName,tokenAddress,chainID)
110
+ // - check that the length of fields separated by fieldSeparator is expectedSplitLength
111
+ // - check that the first split (tokenName) is formatted properly as a LegacyAssetString.
112
+ //
113
+ // NOTE: this function assumes that coreValidate() has already been run.
114
+ func ValidateDefiAssetString (asset string ) error {
115
+ token , _ , _ , err := SplitDefiAssetString (asset )
116
+ if err != nil {
117
+ return err
118
+ }
119
+
120
+ // first element is a ticker, so we require it to pass legacy asset validation:
121
+ if err := ValidateLegacyAssetString (token ); err != nil {
122
+ return fmt .Errorf ("token field %q is invalid: %w" , token , err )
123
+ }
124
+
125
+ return nil
126
+ }
127
+
128
+ // SplitDefiAssetString splits a defi asset by the fieldSeparator and checks that it is the proper length.
129
+ // returns the split string as (token, address, chainID).
130
+ func SplitDefiAssetString (defiString string ) (token , address , chainID string , err error ) {
131
+ split := strings .Split (defiString , fieldSeparator )
132
+ if len (split ) != expectedSplitLength {
133
+ return "" , "" , "" , fmt .Errorf ("asset fields have wrong length, expected: %d got: %d" , expectedSplitLength , len (split ))
134
+ }
135
+ return split [0 ], split [1 ], split [2 ], nil
136
+ }
137
+
138
+ // LegacyValidateBasic checks that the Base / Quote strings in the CurrencyPair are formatted correctly, i.e.
139
+ // Base + Quote are non-empty, and are in upper-case.
140
+ func (cp * CurrencyPair ) LegacyValidateBasic () error {
24
141
// strings must be valid
25
142
if cp .Base == "" || cp .Quote == "" {
26
143
return fmt .Errorf ("empty quote or base string" )
27
144
}
28
145
// check formatting of base / quote
29
146
if strings .ToUpper (cp .Base ) != cp .Base {
30
- return fmt .Errorf ("incorrectly formatted base string, expected: %s got: %s " , strings .ToUpper (cp .Base ), cp .Base )
147
+ return fmt .Errorf ("incorrectly formatted base string, expected: %q got: %q " , strings .ToUpper (cp .Base ), cp .Base )
31
148
}
32
149
if strings .ToUpper (cp .Quote ) != cp .Quote {
33
- return fmt .Errorf ("incorrectly formatted quote string, expected: %s got: %s" , strings .ToUpper (cp .Quote ), cp .Quote )
150
+ return fmt .Errorf ("incorrectly formatted quote string, expected: %q got: %q" , strings .ToUpper (cp .Quote ),
151
+ cp .Quote )
34
152
}
35
153
36
154
if len (cp .Base ) > MaxCPFieldLength || len (cp .Quote ) > MaxCPFieldLength {
@@ -59,21 +177,50 @@ func CurrencyPairString(base, quote string) string {
59
177
return cp .String ()
60
178
}
61
179
180
+ // CurrencyPairFromString creates a currency pair from a string. Non-capitalized inputs are sanitized and the resulting
181
+ // currency pair is validated.
62
182
func CurrencyPairFromString (s string ) (CurrencyPair , error ) {
63
183
split := strings .Split (s , "/" )
64
184
if len (split ) != 2 {
65
- return CurrencyPair {}, fmt .Errorf ("incorrectly formatted CurrencyPair: %s" , s )
185
+ return CurrencyPair {}, fmt .Errorf ("incorrectly formatted CurrencyPair: %q" , s )
186
+ }
187
+
188
+ base , err := sanitizeAssetString (split [0 ])
189
+ if err != nil {
190
+ return CurrencyPair {}, err
66
191
}
192
+
193
+ quote , err := sanitizeAssetString (split [1 ])
194
+ if err != nil {
195
+ return CurrencyPair {}, err
196
+ }
197
+
67
198
cp := CurrencyPair {
68
- Base : strings . ToUpper ( split [ 0 ]) ,
69
- Quote : strings . ToUpper ( split [ 1 ]) ,
199
+ Base : base ,
200
+ Quote : quote ,
70
201
}
71
202
72
203
return cp , cp .ValidateBasic ()
73
204
}
74
205
206
+ func sanitizeAssetString (s string ) (string , error ) {
207
+ if IsLegacyAssetString (s ) {
208
+ s = strings .ToUpper (s )
209
+ } else {
210
+ token , address , chainID , err := SplitDefiAssetString (s )
211
+ if err != nil {
212
+ return "" , fmt .Errorf ("incorrectly formatted asset: %q: %w" , s , err )
213
+ }
214
+
215
+ token = strings .ToUpper (token )
216
+ s = strings .Join ([]string {token , address , chainID }, fieldSeparator )
217
+ }
218
+
219
+ return s , nil
220
+ }
221
+
75
222
// LegacyDecimals returns the number of decimals that the quote will be reported to. If the quote is Ethereum, then
76
- // the number of decimals is 18. Otherwise, the decimals will be reorted to 8.
223
+ // the number of decimals is 18. Otherwise, the decimals will be reported as 8.
77
224
func (cp * CurrencyPair ) LegacyDecimals () int {
78
225
if strings .ToUpper (cp .Quote ) == ethereum {
79
226
return 18
0 commit comments