diff --git a/src/js/ripple/currency.js b/src/js/ripple/currency.js index cd11d313..66a7e603 100644 --- a/src/js/ripple/currency.js +++ b/src/js/ripple/currency.js @@ -1,4 +1,6 @@ -var extend = require('extend'); +'use strict'; + +var extend = require('extend'); var UInt160 = require('./uint160').UInt160; var utils = require('./utils'); var Float = require('./ieee754').Float; @@ -16,8 +18,7 @@ var Currency = extend(function() { // 3-letter code: ... // XXX Should support hex, C++ doesn't currently allow it. - this._value = NaN; - + this._value = NaN; this._update(); }, UInt160); @@ -32,25 +33,37 @@ Currency.HEX_CURRENCY_BAD = '0000000000000000000000005852500000000000'; * Examples: * * USD => currency - * USD - Dollar => currency with optional full currency name - * XAU (-0.5%pa) => XAU with 0.5% effective demurrage rate per year + * USD - Dollar => currency with optional full currency + * name + * XAU (-0.5%pa) => XAU with 0.5% effective demurrage rate + * per year * XAU - Gold (-0.5%pa) => Optionally allowed full currency name - * USD (1%pa) => US dollars with 1% effective interest per year + * USD (1%pa) => US dollars with 1% effective interest + * per year * INR - Indian Rupees => Optional full currency name with spaces - * TYX - 30-Year Treasuries => Optional full currency with numbers and a dash - * TYX - 30-Year Treasuries (1.5%pa) => Optional full currency with numbers, dash and interest rate + * TYX - 30-Year Treasuries => Optional full currency with numbers + * and a dash + * TYX - 30-Year Treasuries (1.5%pa) => Optional full currency with numbers, + * dash and interest rate * - * The regular expression below matches above cases, broken down for better understanding: + * The regular expression below matches above cases, broken down for better + * understanding: * * ^\s* // start with any amount of whitespace - * ([a-zA-Z]{3}|[0-9]{3}) // either 3 letter alphabetic currency-code or 3 digit numeric currency-code. See ISO 4217 - * (\s*-\s*[- \w]+) // optional full currency name following the dash after currency code, - * full currency code can contain letters, numbers and dashes - * (\s*\(-?\d+\.?\d*%pa\))? // optional demurrage rate, has optional - and . notation (-0.5%pa) + * ([a-zA-Z]{3}|[0-9]{3}) // either 3 letter alphabetic currency-code or 3 + * digit numeric currency-code. See ISO 4217 + * (\s*-\s*[- \w]+) // optional full currency name following the dash + * after currency code, full currency code can + * contain letters, numbers and dashes + * (\s*\(-?\d+\.?\d*%pa\))? // optional demurrage rate, has optional - and + * . notation (-0.5%pa) * \s*$ // end with any amount of whitespace * */ -Currency.prototype.human_RE = /^\s*([a-zA-Z0-9]{3})(\s*-\s*[- \w]+)?(\s*\(-?\d+\.?\d*%pa\))?\s*$/; + +/*eslint-disable max-len*/ +Currency.prototype.human_RE = /^\s*([a-zA-Z0-9\<\>\(\)\{\}\[\]\|\?\!\@\#\$\%\^\&]{3})(\s*-\s*[- \w]+)?(\s*\(-?\d+\.?\d*%pa\))?\s*$/; +/*eslint-enable max-len*/ Currency.from_json = function(j, shouldInterpretXrpAsIou) { return (new Currency()).parse_json(j, shouldInterpretXrpAsIou); @@ -58,39 +71,65 @@ Currency.from_json = function(j, shouldInterpretXrpAsIou) { Currency.from_human = function(j, opts) { return (new Currency().parse_human(j, opts)); -} +}; // this._value = NaN on error. Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) { this._value = NaN; - switch (typeof j) { - case 'string': + if (j instanceof Currency) { + this._value = j.copyTo({})._value; + this._update(); + return this; + } - // if an empty string is given, fall back to XRP + switch (typeof j) { + case 'number': + if (!isNaN(j)) { + this.parse_number(j); + } + break; + case 'string': if (!j || j === '0') { - this.parse_hex(shouldInterpretXrpAsIou ? Currency.HEX_CURRENCY_BAD : Currency.HEX_ZERO); + // Empty string or XRP + this.parse_hex(shouldInterpretXrpAsIou + ? Currency.HEX_CURRENCY_BAD + : Currency.HEX_ZERO); + break; + } + + if (j === '1') { + // 'no currency' + this.parse_hex(Currency.HEX_ONE); + break; + } + + if (/^[A-F0-9]{40}$/.test(j)) { + // Hex format + this.parse_hex(j); break; } // match the given string to see if it's in an allowed format - var matches = String(j).match(this.human_RE); + var matches = j.match(this.human_RE); if (matches) { - var currencyCode = matches[1]; // for the currency 'XRP' case // we drop everything else that could have been provided // e.g. 'XRP - Ripple' if (!currencyCode || /^(0|XRP)$/.test(currencyCode)) { - this.parse_hex(shouldInterpretXrpAsIou ? Currency.HEX_CURRENCY_BAD : Currency.HEX_ZERO); + this.parse_hex(shouldInterpretXrpAsIou + ? Currency.HEX_CURRENCY_BAD + : Currency.HEX_ZERO); // early break, we can't have interest on XRP break; } - // the full currency is matched as it is part of the valid currency format, but not stored + // the full currency is matched as it is part of the valid currency + // format, but not stored // var full_currency = matches[2] || ''; var interest = matches[3] || ''; @@ -117,25 +156,28 @@ Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) { currencyData[2] = currencyCode.charCodeAt(1) & 0xff; currencyData[3] = currencyCode.charCodeAt(2) & 0xff; - // byte 5-8 are for reference date, but should always be 0 so we won't fill it + // byte 5-8 are for reference date, but should always be 0 so we + // won't fill it // byte 9-16 are for the interest percentage = parseFloat(percentage[0]); - // the interest or demurrage is expressed as a yearly (per annum) value + // the interest or demurrage is expressed as a yearly (per annum) + // value var secondsPerYear = 31536000; // 60 * 60 * 24 * 365 // Calculating the interest e-fold // 0.5% demurrage is expressed 0.995, 0.005 less than 1 // 0.5% interest is expressed as 1.005, 0.005 more than 1 - var interestEfold = secondsPerYear / Math.log(1 + percentage/100); + var interestEfold = secondsPerYear / Math.log(1 + percentage / 100); var bytes = Float.toIEEE754Double(interestEfold); - for (var i=0; i<=bytes.length; i++) { + for (var i = 0; i <= bytes.length; i++) { currencyData[8 + i] = bytes[i] & 0xff; } - // the last 4 bytes are reserved for future use, so we won't fill those + // the last 4 bytes are reserved for future use, so we won't fill + // those } else { currencyData[12] = currencyCode.charCodeAt(0) & 0xff; @@ -144,21 +186,6 @@ Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) { } this.parse_bytes(currencyData); - } else { - this.parse_hex(j); - } - break; - - case 'number': - if (!isNaN(j)) { - this.parse_number(j); - } - break; - - case 'object': - if (j instanceof Currency) { - this._value = j.copyTo({})._value; - this._update(); } break; } @@ -166,7 +193,6 @@ Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) { return this; }; - Currency.prototype.parse_human = function(j) { return this.parse_json(j); }; @@ -176,6 +202,7 @@ Currency.prototype.parse_human = function(j) { * * You should never need to call this. */ + Currency.prototype._update = function() { var bytes = this.to_bytes(); @@ -183,7 +210,7 @@ Currency.prototype._update = function() { var isZeroExceptInStandardPositions = true; if (!bytes) { - return 'XRP'; + return; } this._native = false; @@ -192,8 +219,9 @@ Currency.prototype._update = function() { this._interest_period = NaN; this._iso_code = ''; - for (var i=0; i<20; i++) { - isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || bytes[i]===0); + for (var i = 0; i < 20; i++) { + isZeroExceptInStandardPositions = isZeroExceptInStandardPositions + && (i === 12 || i === 13 || i === 14 || bytes[i] === 0); } if (isZeroExceptInStandardPositions) { @@ -201,7 +229,7 @@ Currency.prototype._update = function() { + String.fromCharCode(bytes[13]) + String.fromCharCode(bytes[14]); - if (this._iso_code === '\0\0\0') { + if (this._iso_code === '\u0000\u0000\u0000') { this._native = true; this._iso_code = 'XRP'; } @@ -215,8 +243,8 @@ Currency.prototype._update = function() { this._type = 1; this._interest_start = (bytes[4] << 24) + (bytes[5] << 16) + - (bytes[6] << 8) + - (bytes[7] ); + (bytes[6] << 8) + + (bytes[7]); this._interest_period = Float.fromIEEE754Double(bytes.slice(8, 16)); } }; @@ -230,7 +258,8 @@ Currency.prototype.parse_bytes = function(byte_array) { var isZeroExceptInStandardPositions = true; for (var i=0; i<20; i++) { - isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || byte_array[0]===0) + isZeroExceptInStandardPositions = isZeroExceptInStandardPositions + && (i===12 || i===13 || i===14 || byte_array[0]===0) } if (isZeroExceptInStandardPositions) { @@ -260,20 +289,25 @@ Currency.prototype.is_native = function() { }; /** - * Whether this currency is an interest-bearing/demurring currency. + * @return {Boolean} whether this currency is an interest-bearing currency */ + Currency.prototype.has_interest = function() { - return this._type === 1 && !isNaN(this._interest_start) && !isNaN(this._interest_period); + return this._type === 1 + && !isNaN(this._interest_start) + && !isNaN(this._interest_period); }; /** * - * @param referenceDate - number of seconds since the Ripple Epoch (0:00 on January 1, 2000 UTC) - * used to calculate the interest over provided interval - * pass in one years worth of seconds to ge the yearly interest - * @returns {number} - interest for provided interval, can be negative for demurred currencies + * @param {number} referenceDate number of seconds since the Ripple Epoch + * (0:00 on January 1, 2000 UTC) used to calculate the + * interest over provided interval pass in one years + * worth of seconds to ge the yearly interest + * @returns {number} interest for provided interval, can be negative for + * demurred currencies */ -Currency.prototype.get_interest_at = function(referenceDate, decimals) { +Currency.prototype.get_interest_at = function(referenceDate) { if (!this.has_interest()) { return 0; } @@ -288,18 +322,20 @@ Currency.prototype.get_interest_at = function(referenceDate, decimals) { } // calculate interest by e-fold number - return Math.exp((referenceDate - this._interest_start) / this._interest_period); + return Math.exp((referenceDate - this._interest_start) + / this._interest_period); }; -Currency.prototype.get_interest_percentage_at = function(referenceDate, decimals) { +Currency.prototype.get_interest_percentage_at += function(referenceDate, decimals) { var interest = this.get_interest_at(referenceDate, decimals); // convert to percentage - var interest = (interest*100)-100; - var decimalMultiplier = decimals ? Math.pow(10,decimals) : 100; + interest = (interest * 100) - 100; + var decimalMultiplier = decimals ? Math.pow(10, decimals) : 100; // round to two decimals behind the dot - return Math.round(interest*decimalMultiplier) / decimalMultiplier; + return Math.round(interest * decimalMultiplier) / decimalMultiplier; }; // XXX Currently we inherit UInt.prototype.is_valid, which is mostly fine. @@ -307,9 +343,9 @@ Currency.prototype.get_interest_percentage_at = function(referenceDate, decimals // We could be doing further checks into the internal format of the // currency data, since there are some values that are invalid. // -//Currency.prototype.is_valid = function() { +// Currency.prototype.is_valid = function() { // return UInt.prototype.is_valid() && ...; -//}; +// }; Currency.prototype.to_json = function(opts) { if (!this.is_valid()) { @@ -317,28 +353,35 @@ Currency.prototype.to_json = function(opts) { return 'XRP'; } - var opts = opts || {}; + if (!opts) { + opts = {}; + } var currency; var fullName = opts && opts.full_name ? ' - ' + opts.full_name : ''; - opts.show_interest = opts.show_interest !== void(0) ? opts.show_interest : this.has_interest(); + opts.show_interest = opts.show_interest !== undefined + ? opts.show_interest + : this.has_interest(); if (!opts.force_hex && /^[A-Z0-9]{3}$/.test(this._iso_code)) { currency = this._iso_code + fullName; if (opts.show_interest) { - var decimals = !isNaN(opts.decimals) ? opts.decimals : void(0); - var interestPercentage = this.has_interest() ? this.get_interest_percentage_at(this._interest_start + 3600 * 24 * 365, decimals) : 0; + var decimals = !isNaN(opts.decimals) ? opts.decimals : undefined; + var interestPercentage = this.has_interest() + ? this.get_interest_percentage_at( + this._interest_start + 3600 * 24 * 365, decimals + ) + : 0; currency += ' (' + interestPercentage + '%pa)'; } } else { - // Fallback to returning the raw currency hex currency = this.to_hex(); - // XXX This is to maintain backwards compatibility, but it is very, very odd - // behavior, so we should deprecate it and get rid of it as soon as - // possible. + // XXX This is to maintain backwards compatibility, but it is very, very + // odd behavior, so we should deprecate it and get rid of it as soon as + // possible. if (currency === Currency.HEX_ONE) { currency = 1; } @@ -357,5 +400,3 @@ Currency.prototype.get_iso = function() { }; exports.Currency = Currency; - -// vim:sw=2:sts=2:ts=8:et diff --git a/test/currency-test.js b/test/currency-test.js index 7bf3ac68..8f1e0511 100644 --- a/test/currency-test.js +++ b/test/currency-test.js @@ -1,3 +1,5 @@ +/*eslint-disable */ + var assert = require('assert'); var currency = require('ripple-lib').Currency; var timeUtil = require('ripple-lib').utils.time; @@ -54,6 +56,16 @@ describe('Currency', function() { assert(r.is_valid()); assert.strictEqual('1D2', r.to_json()); }); + it('from_json("1").to_human()', function() { + var r = currency.from_json('1'); + assert(r.is_valid()); + assert.strictEqual(1, r.to_json()); + }); + it('from_json("#$%").to_human()', function() { + var r = currency.from_json('#$%'); + assert(r.is_valid()); + assert.strictEqual('0000000000000000000000002324250000000000', r.to_json()); + }); it('from_json("XAU").to_json() hex', function() { var r = currency.from_json("XAU"); assert.strictEqual('0000000000000000000000005841550000000000', r.to_json({force_hex: true}));