Skip to content

Commit 2166bb2

Browse files
committed
Fix currency parsing of non-alphanumeric and no-currency currencies
1 parent a9b7d7d commit 2166bb2

File tree

2 files changed

+127
-74
lines changed

2 files changed

+127
-74
lines changed

src/js/ripple/currency.js

+115-74
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
var extend = require('extend');
1+
'use strict';
2+
3+
var extend = require('extend');
24
var UInt160 = require('./uint160').UInt160;
35
var utils = require('./utils');
46
var Float = require('./ieee754').Float;
@@ -16,8 +18,7 @@ var Currency = extend(function() {
1618
// 3-letter code: ...
1719
// XXX Should support hex, C++ doesn't currently allow it.
1820

19-
this._value = NaN;
20-
21+
this._value = NaN;
2122
this._update();
2223
}, UInt160);
2324

@@ -32,65 +33,103 @@ Currency.HEX_CURRENCY_BAD = '0000000000000000000000005852500000000000';
3233
* Examples:
3334
*
3435
* USD => currency
35-
* USD - Dollar => currency with optional full currency name
36-
* XAU (-0.5%pa) => XAU with 0.5% effective demurrage rate per year
36+
* USD - Dollar => currency with optional full currency
37+
* name
38+
* XAU (-0.5%pa) => XAU with 0.5% effective demurrage rate
39+
* per year
3740
* XAU - Gold (-0.5%pa) => Optionally allowed full currency name
38-
* USD (1%pa) => US dollars with 1% effective interest per year
41+
* USD (1%pa) => US dollars with 1% effective interest
42+
* per year
3943
* INR - Indian Rupees => Optional full currency name with spaces
40-
* TYX - 30-Year Treasuries => Optional full currency with numbers and a dash
41-
* TYX - 30-Year Treasuries (1.5%pa) => Optional full currency with numbers, dash and interest rate
44+
* TYX - 30-Year Treasuries => Optional full currency with numbers
45+
* and a dash
46+
* TYX - 30-Year Treasuries (1.5%pa) => Optional full currency with numbers,
47+
* dash and interest rate
4248
*
43-
* The regular expression below matches above cases, broken down for better understanding:
49+
* The regular expression below matches above cases, broken down for better
50+
* understanding:
4451
*
4552
* ^\s* // start with any amount of whitespace
46-
* ([a-zA-Z]{3}|[0-9]{3}) // either 3 letter alphabetic currency-code or 3 digit numeric currency-code. See ISO 4217
47-
* (\s*-\s*[- \w]+) // optional full currency name following the dash after currency code,
48-
* full currency code can contain letters, numbers and dashes
49-
* (\s*\(-?\d+\.?\d*%pa\))? // optional demurrage rate, has optional - and . notation (-0.5%pa)
53+
* ([a-zA-Z]{3}|[0-9]{3}) // either 3 letter alphabetic currency-code or 3
54+
* digit numeric currency-code. See ISO 4217
55+
* (\s*-\s*[- \w]+) // optional full currency name following the dash
56+
* after currency code, full currency code can
57+
* contain letters, numbers and dashes
58+
* (\s*\(-?\d+\.?\d*%pa\))? // optional demurrage rate, has optional - and
59+
* . notation (-0.5%pa)
5060
* \s*$ // end with any amount of whitespace
5161
*
5262
*/
53-
Currency.prototype.human_RE = /^\s*([a-zA-Z0-9]{3})(\s*-\s*[- \w]+)?(\s*\(-?\d+\.?\d*%pa\))?\s*$/;
63+
64+
/*eslint-disable max-len*/
65+
Currency.prototype.human_RE = /^\s*([a-zA-Z0-9\<\>\(\)\{\}\[\]\|\?\!\@\#\$\%\^\&]{3})(\s*-\s*[- \w]+)?(\s*\(-?\d+\.?\d*%pa\))?\s*$/;
66+
/*eslint-enable max-len*/
5467

5568
Currency.from_json = function(j, shouldInterpretXrpAsIou) {
5669
return (new Currency()).parse_json(j, shouldInterpretXrpAsIou);
5770
};
5871

5972
Currency.from_human = function(j, opts) {
6073
return (new Currency().parse_human(j, opts));
61-
}
74+
};
6275

6376
// this._value = NaN on error.
6477
Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
6578
this._value = NaN;
6679

80+
if (j instanceof Currency) {
81+
this._value = j.copyTo({})._value;
82+
this._update();
83+
return this;
84+
}
85+
6786
switch (typeof j) {
87+
case 'number':
88+
if (!isNaN(j)) {
89+
this.parse_number(j);
90+
}
91+
break;
6892
case 'string':
69-
70-
// if an empty string is given, fall back to XRP
7193
if (!j || j === '0') {
72-
this.parse_hex(shouldInterpretXrpAsIou ? Currency.HEX_CURRENCY_BAD : Currency.HEX_ZERO);
94+
// Empty string or XRP
95+
this.parse_hex(shouldInterpretXrpAsIou
96+
? Currency.HEX_CURRENCY_BAD
97+
: Currency.HEX_ZERO);
98+
break;
99+
}
100+
101+
if (j === '1') {
102+
// 'no currency'
103+
this.parse_hex(Currency.HEX_ONE);
104+
break;
105+
}
106+
107+
if (/^[A-F0-9]{40}$/.test(j)) {
108+
// Hex format
109+
this.parse_hex(j);
73110
break;
74111
}
75112

76113
// match the given string to see if it's in an allowed format
77-
var matches = String(j).match(this.human_RE);
114+
var matches = j.match(this.human_RE);
78115

79116
if (matches) {
80-
81117
var currencyCode = matches[1];
82118

83119
// for the currency 'XRP' case
84120
// we drop everything else that could have been provided
85121
// e.g. 'XRP - Ripple'
86122
if (!currencyCode || /^(0|XRP)$/.test(currencyCode)) {
87-
this.parse_hex(shouldInterpretXrpAsIou ? Currency.HEX_CURRENCY_BAD : Currency.HEX_ZERO);
123+
this.parse_hex(shouldInterpretXrpAsIou
124+
? Currency.HEX_CURRENCY_BAD
125+
: Currency.HEX_ZERO);
88126

89127
// early break, we can't have interest on XRP
90128
break;
91129
}
92130

93-
// the full currency is matched as it is part of the valid currency format, but not stored
131+
// the full currency is matched as it is part of the valid currency
132+
// format, but not stored
94133
// var full_currency = matches[2] || '';
95134
var interest = matches[3] || '';
96135

@@ -117,25 +156,28 @@ Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
117156
currencyData[2] = currencyCode.charCodeAt(1) & 0xff;
118157
currencyData[3] = currencyCode.charCodeAt(2) & 0xff;
119158

120-
// byte 5-8 are for reference date, but should always be 0 so we won't fill it
159+
// byte 5-8 are for reference date, but should always be 0 so we
160+
// won't fill it
121161

122162
// byte 9-16 are for the interest
123163
percentage = parseFloat(percentage[0]);
124164

125-
// the interest or demurrage is expressed as a yearly (per annum) value
165+
// the interest or demurrage is expressed as a yearly (per annum)
166+
// value
126167
var secondsPerYear = 31536000; // 60 * 60 * 24 * 365
127168

128169
// Calculating the interest e-fold
129170
// 0.5% demurrage is expressed 0.995, 0.005 less than 1
130171
// 0.5% interest is expressed as 1.005, 0.005 more than 1
131-
var interestEfold = secondsPerYear / Math.log(1 + percentage/100);
172+
var interestEfold = secondsPerYear / Math.log(1 + percentage / 100);
132173
var bytes = Float.toIEEE754Double(interestEfold);
133174

134-
for (var i=0; i<=bytes.length; i++) {
175+
for (var i = 0; i <= bytes.length; i++) {
135176
currencyData[8 + i] = bytes[i] & 0xff;
136177
}
137178

138-
// the last 4 bytes are reserved for future use, so we won't fill those
179+
// the last 4 bytes are reserved for future use, so we won't fill
180+
// those
139181

140182
} else {
141183
currencyData[12] = currencyCode.charCodeAt(0) & 0xff;
@@ -144,29 +186,13 @@ Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
144186
}
145187

146188
this.parse_bytes(currencyData);
147-
} else {
148-
this.parse_hex(j);
149-
}
150-
break;
151-
152-
case 'number':
153-
if (!isNaN(j)) {
154-
this.parse_number(j);
155-
}
156-
break;
157-
158-
case 'object':
159-
if (j instanceof Currency) {
160-
this._value = j.copyTo({})._value;
161-
this._update();
162189
}
163190
break;
164191
}
165192

166193
return this;
167194
};
168195

169-
170196
Currency.prototype.parse_human = function(j) {
171197
return this.parse_json(j);
172198
};
@@ -176,14 +202,15 @@ Currency.prototype.parse_human = function(j) {
176202
*
177203
* You should never need to call this.
178204
*/
205+
179206
Currency.prototype._update = function() {
180207
var bytes = this.to_bytes();
181208

182209
// is it 0 everywhere except 12, 13, 14?
183210
var isZeroExceptInStandardPositions = true;
184211

185212
if (!bytes) {
186-
return 'XRP';
213+
return;
187214
}
188215

189216
this._native = false;
@@ -192,16 +219,17 @@ Currency.prototype._update = function() {
192219
this._interest_period = NaN;
193220
this._iso_code = '';
194221

195-
for (var i=0; i<20; i++) {
196-
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || bytes[i]===0);
222+
for (var i = 0; i < 20; i++) {
223+
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions
224+
&& (i === 12 || i === 13 || i === 14 || bytes[i] === 0);
197225
}
198226

199227
if (isZeroExceptInStandardPositions) {
200228
this._iso_code = String.fromCharCode(bytes[12])
201229
+ String.fromCharCode(bytes[13])
202230
+ String.fromCharCode(bytes[14]);
203231

204-
if (this._iso_code === '\0\0\0') {
232+
if (this._iso_code === '\u0000\u0000\u0000') {
205233
this._native = true;
206234
this._iso_code = 'XRP';
207235
}
@@ -215,8 +243,8 @@ Currency.prototype._update = function() {
215243
this._type = 1;
216244
this._interest_start = (bytes[4] << 24) +
217245
(bytes[5] << 16) +
218-
(bytes[6] << 8) +
219-
(bytes[7] );
246+
(bytes[6] << 8) +
247+
(bytes[7]);
220248
this._interest_period = Float.fromIEEE754Double(bytes.slice(8, 16));
221249
}
222250
};
@@ -230,7 +258,8 @@ Currency.prototype.parse_bytes = function(byte_array) {
230258
var isZeroExceptInStandardPositions = true;
231259
232260
for (var i=0; i<20; i++) {
233-
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || byte_array[0]===0)
261+
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions
262+
&& (i===12 || i===13 || i===14 || byte_array[0]===0)
234263
}
235264
236265
if (isZeroExceptInStandardPositions) {
@@ -260,20 +289,25 @@ Currency.prototype.is_native = function() {
260289
};
261290

262291
/**
263-
* Whether this currency is an interest-bearing/demurring currency.
292+
* @return {Boolean} whether this currency is an interest-bearing currency
264293
*/
294+
265295
Currency.prototype.has_interest = function() {
266-
return this._type === 1 && !isNaN(this._interest_start) && !isNaN(this._interest_period);
296+
return this._type === 1
297+
&& !isNaN(this._interest_start)
298+
&& !isNaN(this._interest_period);
267299
};
268300

269301
/**
270302
*
271-
* @param referenceDate - number of seconds since the Ripple Epoch (0:00 on January 1, 2000 UTC)
272-
* used to calculate the interest over provided interval
273-
* pass in one years worth of seconds to ge the yearly interest
274-
* @returns {number} - interest for provided interval, can be negative for demurred currencies
303+
* @param {number} referenceDate number of seconds since the Ripple Epoch
304+
* (0:00 on January 1, 2000 UTC) used to calculate the
305+
* interest over provided interval pass in one years
306+
* worth of seconds to ge the yearly interest
307+
* @returns {number} interest for provided interval, can be negative for
308+
* demurred currencies
275309
*/
276-
Currency.prototype.get_interest_at = function(referenceDate, decimals) {
310+
Currency.prototype.get_interest_at = function(referenceDate) {
277311
if (!this.has_interest()) {
278312
return 0;
279313
}
@@ -288,57 +322,66 @@ Currency.prototype.get_interest_at = function(referenceDate, decimals) {
288322
}
289323

290324
// calculate interest by e-fold number
291-
return Math.exp((referenceDate - this._interest_start) / this._interest_period);
325+
return Math.exp((referenceDate - this._interest_start)
326+
/ this._interest_period);
292327
};
293328

294-
Currency.prototype.get_interest_percentage_at = function(referenceDate, decimals) {
329+
Currency.prototype.get_interest_percentage_at
330+
= function(referenceDate, decimals) {
295331
var interest = this.get_interest_at(referenceDate, decimals);
296332

297333
// convert to percentage
298-
var interest = (interest*100)-100;
299-
var decimalMultiplier = decimals ? Math.pow(10,decimals) : 100;
334+
interest = (interest * 100) - 100;
335+
var decimalMultiplier = decimals ? Math.pow(10, decimals) : 100;
300336

301337
// round to two decimals behind the dot
302-
return Math.round(interest*decimalMultiplier) / decimalMultiplier;
338+
return Math.round(interest * decimalMultiplier) / decimalMultiplier;
303339
};
304340

305341
// XXX Currently we inherit UInt.prototype.is_valid, which is mostly fine.
306342
//
307343
// We could be doing further checks into the internal format of the
308344
// currency data, since there are some values that are invalid.
309345
//
310-
//Currency.prototype.is_valid = function() {
346+
// Currency.prototype.is_valid = function() {
311347
// return UInt.prototype.is_valid() && ...;
312-
//};
348+
// };
313349

314350
Currency.prototype.to_json = function(opts) {
315351
if (!this.is_valid()) {
316352
// XXX This is backwards compatible behavior, but probably not very good.
317353
return 'XRP';
318354
}
319355

320-
var opts = opts || {};
356+
if (!opts) {
357+
opts = {};
358+
}
321359

322360
var currency;
323361
var fullName = opts && opts.full_name ? ' - ' + opts.full_name : '';
324-
opts.show_interest = opts.show_interest !== void(0) ? opts.show_interest : this.has_interest();
362+
opts.show_interest = opts.show_interest !== undefined
363+
? opts.show_interest
364+
: this.has_interest();
325365

326366
if (!opts.force_hex && /^[A-Z0-9]{3}$/.test(this._iso_code)) {
327367
currency = this._iso_code + fullName;
328368
if (opts.show_interest) {
329-
var decimals = !isNaN(opts.decimals) ? opts.decimals : void(0);
330-
var interestPercentage = this.has_interest() ? this.get_interest_percentage_at(this._interest_start + 3600 * 24 * 365, decimals) : 0;
369+
var decimals = !isNaN(opts.decimals) ? opts.decimals : undefined;
370+
var interestPercentage = this.has_interest()
371+
? this.get_interest_percentage_at(
372+
this._interest_start + 3600 * 24 * 365, decimals
373+
)
374+
: 0;
331375
currency += ' (' + interestPercentage + '%pa)';
332376
}
333377

334378
} else {
335-
336379
// Fallback to returning the raw currency hex
337380
currency = this.to_hex();
338381

339-
// XXX This is to maintain backwards compatibility, but it is very, very odd
340-
// behavior, so we should deprecate it and get rid of it as soon as
341-
// possible.
382+
// XXX This is to maintain backwards compatibility, but it is very, very
383+
// odd behavior, so we should deprecate it and get rid of it as soon as
384+
// possible.
342385
if (currency === Currency.HEX_ONE) {
343386
currency = 1;
344387
}
@@ -357,5 +400,3 @@ Currency.prototype.get_iso = function() {
357400
};
358401

359402
exports.Currency = Currency;
360-
361-
// vim:sw=2:sts=2:ts=8:et

0 commit comments

Comments
 (0)