diff --git a/src/js/ripple/amount.js b/src/js/ripple/amount.js index 93cec17b..6e257bb1 100644 --- a/src/js/ripple/amount.js +++ b/src/js/ripple/amount.js @@ -598,7 +598,7 @@ Amount.prototype.invert = function() { * The regular expression below matches above cases, broken down for better understanding: * * ^\s* // start with any amount of whitespace - * ([a-z]{3})? // optional any 3 letters + * ([a-zA-Z]{3}|[0-9]{3}) // either 3 letter alphabetic currency-code or 3 digit numeric currency-code. See ISO 4217 * \s* // any amount of whitespace * (-)? // optional dash * (\d+) // 1 or more digits @@ -609,7 +609,7 @@ Amount.prototype.invert = function() { * $ // end of string * */ -Amount.human_RE = /^\s*([a-z]{3})?\s*(-)?(\d+)(\.(\d*))?\s*([a-f0-9]{40}|[a-z0-9]{3})?\s*$/i; +Amount.human_RE = /^\s*([a-z]{3}|[0-9]{3})?\s*(-)?(\d+)(\.(\d*))?\s*([a-f0-9]{40}|[a-z0-9]{3})?\s*$/i; Amount.prototype.parse_human = function(j, opts) { opts = opts || {}; @@ -764,7 +764,7 @@ Amount.prototype.parse_number = function(n) { this._is_native = false; this._currency = Currency.from_json(1); this._issuer = UInt160.from_json(1); - this._is_negative = n < 0 ? 1 : 0; + this._is_negative = n < 0 ? true : false; this._value = new BigInteger(String(this._is_negative ? -n : n)); this._offset = 0; diff --git a/src/js/ripple/currency.js b/src/js/ripple/currency.js index e4fda84e..0dc8724e 100644 --- a/src/js/ripple/currency.js +++ b/src/js/ripple/currency.js @@ -1,7 +1,7 @@ var extend = require('extend'); var UInt160 = require('./uint160').UInt160; -var Float = require('./float').Float; var utils = require('./utils'); +var Float = require('./ieee754').Float; // // Currency support @@ -26,6 +26,32 @@ Currency.prototype.constructor = Currency; Currency.HEX_CURRENCY_BAD = '0000000000000000000000005852500000000000'; +/** + * Tries to correctly interpret a Currency as entered by a user. + * + * Examples: + * + * USD => currency + * 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 + * 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 + * + * 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) + * \s*$ // end with any amount of whitespace + * + */ +Currency.prototype.human_RE = /^\s*([a-zA-Z]{3}|[0-9]{3})(\s*-\s*[- \w]+)?(\s*\(-?\d+\.?\d*%pa\))?\s*$/; + Currency.from_json = function(j, shouldInterpretXrpAsIou) { if (j instanceof this) { return j.clone(); @@ -34,24 +60,85 @@ 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 || /^(0|XRP)$/.test(j)) { if (shouldInterpretXrpAsIou) { this.parse_hex(Currency.HEX_CURRENCY_BAD); } else { this.parse_hex(Currency.HEX_ZERO); } - } else if (/^[a-zA-Z0-9]{3}$/.test(j)) { - var currencyCode = j.toUpperCase(); + break; + } + + // match the given string to see if it's in an allowed format + var matches = String(j).match(this.human_RE); + + if (matches) { + + var currencyCode = matches[1]; + // 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] || ''; + + // interest is defined as interest per year, per annum (pa) + var percentage = interest.match(/(-?\d+\.?\d+)/); + + currencyCode = currencyCode.toUpperCase(); + var currencyData = utils.arraySet(20, 0); - currencyData[12] = currencyCode.charCodeAt(0) & 0xff; - currencyData[13] = currencyCode.charCodeAt(1) & 0xff; - currencyData[14] = currencyCode.charCodeAt(2) & 0xff; + + if (percentage) { + /* + * 20 byte layout of a interest bearing currency + * + * 01 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ + * CURCODE- DATE------- RATE------------------- RESERVED--- + */ + + // byte 1 for type, use '1' to denote demurrage currency + currencyData[0] = 1; + + // byte 2-4 for currency code + currencyData[1] = currencyCode.charCodeAt(0) & 0xff; + 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 9-16 are for the interest + percentage = parseFloat(percentage[0]); + + // 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 bytes = Float.toIEEE754Double(interestEfold); + + 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 + + } else { + currencyData[12] = currencyCode.charCodeAt(0) & 0xff; + currencyData[13] = currencyCode.charCodeAt(1) & 0xff; + currencyData[14] = currencyCode.charCodeAt(2) & 0xff; + } + this.parse_bytes(currencyData); } else { this.parse_hex(j); @@ -75,6 +162,11 @@ Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) { return this; }; + +Currency.prototype.parse_human = function(j) { + return this.parse_json(j); +}; + /** * Recalculate internal representation. * @@ -92,7 +184,7 @@ Currency.prototype._update = function() { this._native = false; this._type = -1; - this._interest_start = new Date(); + this._interest_start = NaN; this._interest_period = NaN; this._iso_code = ''; @@ -121,7 +213,7 @@ Currency.prototype._update = function() { (bytes[5] << 16) + (bytes[6] << 8) + (bytes[7] ); - this._interest_period = Float.fromBytes(bytes.slice(8, 16)); + this._interest_period = Float.fromIEEE754Double(bytes.slice(8, 16)); } }; @@ -167,21 +259,45 @@ Currency.prototype.is_native = function() { * Whether this currency is an interest-bearing/demurring currency. */ Currency.prototype.has_interest = function() { - return this._type === 1 && this._interest_start && !isNaN(this._interest_period); + return this._type === 1 && !isNaN(this._interest_start) && !isNaN(this._interest_period); }; -Currency.prototype.get_interest_at = function(referenceDate) { +/** + * + * @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 + */ +Currency.prototype.get_interest_at = function(referenceDate, decimals) { if (!this.has_interest) { - return 1; + return 0; + } + + // use one year as a default period + if (!referenceDate) { + referenceDate = this._interest_start + 3600 * 24 * 365; } if (referenceDate instanceof Date) { referenceDate = utils.fromTimestamp(referenceDate.getTime()); } + // calculate interest by e-fold number return Math.exp((referenceDate - this._interest_start) / this._interest_period); }; +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; + + // round to two decimals behind the dot + return Math.round(interest*decimalMultiplier) / decimalMultiplier; +}; + // XXX Currently we inherit UInt.prototype.is_valid, which is mostly fine. // // We could be doing further checks into the internal format of the @@ -191,38 +307,43 @@ Currency.prototype.get_interest_at = function(referenceDate) { // return this._value instanceof BigInteger && ...; //}; -Currency.prototype.to_json = function() { +Currency.prototype.to_json = function(opts) { if (!this.is_valid()) { // XXX This is backwards compatible behavior, but probably not very good. return 'XRP'; } + var currency; + var fullName = opts && opts.full_name ? " - " + opts.full_name : ""; + // Any currency with standard properties and a valid code can be abbreviated // in the JSON wire format as the three character code. if (/^[A-Z0-9]{3}$/.test(this._iso_code) && !this.has_interest()) { - return this._iso_code; + currency = this._iso_code + fullName; + + // If there is interest, append the annual interest to the full currency name + } else if (this.has_interest()) { + var decimals = opts ? opts.decimals : undefined; + currency = this._iso_code + fullName + " (" + this.get_interest_percentage_at(this._interest_start + 3600 * 24 * 365, decimals) + "%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. + if (currency === Currency.HEX_ONE) { + currency = 1; + } } - // Fallback to returning the raw currency hex - var currencyHex = 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. - if (currencyHex === Currency.HEX_ONE) { - return 1; - } - - return currencyHex; + return currency; }; -Currency.prototype.to_human = function() { +Currency.prototype.to_human = function(opts) { // to_human() will always print the human-readable currency code if available. - if (/^[A-Z0-9]{3}$/.test(this._iso_code)) { - return this._iso_code; - } - - return this.to_json(); + return this.to_json(opts); }; exports.Currency = Currency; diff --git a/src/js/ripple/ieee754.js b/src/js/ripple/ieee754.js new file mode 100644 index 00000000..d5d61878 --- /dev/null +++ b/src/js/ripple/ieee754.js @@ -0,0 +1,107 @@ +// Convert a JavaScript number to IEEE-754 Double Precision +// value represented as an array of 8 bytes (octets) +// +// Based on: +// http://cautionsingularityahead.blogspot.com/2010/04/javascript-and-ieee754-redux.html +// +// Found and modified from: +// https://gist.github.com/bartaz/1119041 + +var Float = exports.Float = {}; + +Float.toIEEE754 = function(v, ebits, fbits) { + + var bias = (1 << (ebits - 1)) - 1; + + // Compute sign, exponent, fraction + var s, e, f; + if (isNaN(v)) { + e = (1 << bias) - 1; f = 1; s = 0; + } + else if (v === Infinity || v === -Infinity) { + e = (1 << bias) - 1; f = 0; s = (v < 0) ? 1 : 0; + } + else if (v === 0) { + e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0; + } + else { + s = v < 0; + v = Math.abs(v); + + if (v >= Math.pow(2, 1 - bias)) { + var ln = Math.min(Math.floor(Math.log(v) / Math.LN2), bias); + e = ln + bias; + f = v * Math.pow(2, fbits - ln) - Math.pow(2, fbits); + } + else { + e = 0; + f = v / Math.pow(2, 1 - bias - fbits); + } + } + + // Pack sign, exponent, fraction + var i, bits = []; + for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = Math.floor(f / 2); } + for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = Math.floor(e / 2); } + bits.push(s ? 1 : 0); + bits.reverse(); + var str = bits.join(''); + + // Bits to bytes + var bytes = []; + while (str.length) { + bytes.push(parseInt(str.substring(0, 8), 2)); + str = str.substring(8); + } + return bytes; +} + +Float.fromIEEE754 = function(bytes, ebits, fbits) { + + // Bytes to bits + var bits = []; + for (var i = bytes.length; i; i -= 1) { + var byte = bytes[i - 1]; + for (var j = 8; j; j -= 1) { + bits.push(byte % 2 ? 1 : 0); byte = byte >> 1; + } + } + bits.reverse(); + var str = bits.join(''); + + // Unpack sign, exponent, fraction + var bias = (1 << (ebits - 1)) - 1; + var s = parseInt(str.substring(0, 1), 2) ? -1 : 1; + var e = parseInt(str.substring(1, 1 + ebits), 2); + var f = parseInt(str.substring(1 + ebits), 2); + + // Produce number + if (e === (1 << ebits) - 1) { + return f !== 0 ? NaN : s * Infinity; + } + else if (e > 0) { + return s * Math.pow(2, e - bias) * (1 + f / Math.pow(2, fbits)); + } + else if (f !== 0) { + return s * Math.pow(2, -(bias-1)) * (f / Math.pow(2, fbits)); + } + else { + return s * 0; + } +} + +Float.fromIEEE754Double = function(b) { return Float.fromIEEE754(b, 11, 52); } +Float.toIEEE754Double = function(v) { return Float.toIEEE754(v, 11, 52); } +Float.fromIEEE754Single = function(b) { return Float.fromIEEE754(b, 8, 23); } +Float.toIEEE754Single = function(v) { return Float.toIEEE754(v, 8, 23); } + + +// Convert array of octets to string binary representation +// by bartaz + +Float.toIEEE754DoubleString = function(v) { + return exports.toIEEE754Double(v) + .map(function(n){ for(n = n.toString(2);n.length < 8;n="0"+n); return n }) + .join('') + .replace(/(.)(.{11})(.{52})/, "$1 $2 $3") +} \ No newline at end of file diff --git a/test/amount-test.js b/test/amount-test.js index 0630470c..26ec9fc7 100644 --- a/test/amount-test.js +++ b/test/amount-test.js @@ -20,6 +20,21 @@ describe('Amount', function() { it('Number 1', function() { assert.strictEqual(Amount.from_number(1).to_text_full(), '1/1/rrrrrrrrrrrrrrrrrrrrBZbvji'); }); + it('Number 2', function() { + assert.strictEqual(Amount.from_number(2).to_text_full(), '2/1/rrrrrrrrrrrrrrrrrrrrBZbvji'); + }); + it('Multiply 2 "1" with 3 "1", by product_human', function () { + assert.strictEqual(Amount.from_number(2).product_human(Amount.from_number(3)).to_text_full(), '6/1/rrrrrrrrrrrrrrrrrrrrBZbvji'); + }); + it('Multiply 3 USD with 3 "1"', function () { + assert.strictEqual(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_number(3)).to_text_full(), '9/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); + }); + it('Multiply -1 "1" with 3 USD', function () { + assert.strictEqual(Amount.from_number(-1).multiply(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full(), '-3/1/rrrrrrrrrrrrrrrrrrrrBZbvji'); + }); + it('Multiply -1 "1" with 3 USD, by product_human', function () { + assert.strictEqual(Amount.from_number(-1).product_human(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full(), '-3/1/rrrrrrrrrrrrrrrrrrrrBZbvji'); + }); }); describe('text_full_rewrite', function() { it('Number 1', function() { @@ -128,6 +143,12 @@ describe('Amount', function() { it('Parse -0.0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () { assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-0.0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full()); }); + it('Parse 0.0/111/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () { + assert.strictEqual('0/111/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/111/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full()); + }); + it('Parse 0.0/12D/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () { + assert.strictEqual('0/XRP/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/12D/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full()); + }); }); describe('Amount operations', function() { it('Negate native 123', function () { @@ -551,10 +572,10 @@ describe('Amount', function() { assert.strictEqual(Amount.from_quality('F72C7A9EAE4A45ED1FB547AD037D07B9B965C6E662BEBAFA4A03F2A976804235', 'XRP', NaN, {inverse: true, base_currency: '015841551A748AD2C1F76FF6ECB0CCCD00000000', reference_date: 443845330 + 31535000}).to_text_full(), '90,442.196677/XRP'); }); it('USD/XAU(dem)', function () { - assert.strictEqual(Amount.from_quality('4743E58E44974B325D42FD2BB683A6E36950F350EE46DD3A521B644B99782F5F', '015841551A748AD2C1F76FF6ECB0CCCD00000000', 'rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN', {base_currency: 'USD', reference_date: 443845330 + 31535000}).to_text_full(), '0.007710100231303007/015841551A748AD2C1F76FF6ECB0CCCD00000000/rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN'); + assert.strictEqual(Amount.from_quality('4743E58E44974B325D42FD2BB683A6E36950F350EE46DD3A521B644B99782F5F', '015841551A748AD2C1F76FF6ECB0CCCD00000000', 'rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN', {base_currency: 'USD', reference_date: 443845330 + 31535000}).to_text_full(), '0.007710100231303007/XAU (-0.5%pa)/rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN'); }); it('USD/XAU(dem) inverse', function () { - assert.strictEqual(Amount.from_quality('CDFD3AFB2F8C5DBEF75B081F7C957FF5509563266F28F36C5704A0FB0BAD8800', '015841551A748AD2C1F76FF6ECB0CCCD00000000', 'rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN', {inverse: true, base_currency: 'USD', reference_date: 443845330 + 31535000}).to_text_full(), '0.007675186123263489/015841551A748AD2C1F76FF6ECB0CCCD00000000/rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN'); + assert.strictEqual(Amount.from_quality('CDFD3AFB2F8C5DBEF75B081F7C957FF5509563266F28F36C5704A0FB0BAD8800', '015841551A748AD2C1F76FF6ECB0CCCD00000000', 'rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN', {inverse: true, base_currency: 'USD', reference_date: 443845330 + 31535000}).to_text_full(), '0.007675186123263489/XAU (-0.5%pa)/rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN'); }); }); }); diff --git a/test/currency-test.js b/test/currency-test.js index ee2d5e54..98d31b76 100644 --- a/test/currency-test.js +++ b/test/currency-test.js @@ -10,9 +10,9 @@ describe('Currency', function() { it('json_rewrite("NaN") == "XRP"', function() { assert.strictEqual('XRP', currency.json_rewrite(NaN)); }); - it('json_rewrite("015841551A748AD2C1F76FF6ECB0CCCD00000000") == "015841551A748AD2C1F76FF6ECB0CCCD00000000"', function() { + it('json_rewrite("015841551A748AD2C1F76FF6ECB0CCCD00000000") == "XAU (-0.5%pa)"', function() { assert.strictEqual(currency.json_rewrite("015841551A748AD2C1F76FF6ECB0CCCD00000000"), - '015841551A748AD2C1F76FF6ECB0CCCD00000000'); + "XAU (-0.5%pa)"); }); }); describe('from_json', function() { @@ -27,7 +27,60 @@ describe('Currency', function() { assert(r.is_native()); assert.strictEqual('XRP', r.to_json()); }); + it('from_json("111").to_human()', function() { + var r = currency.from_json("111"); + assert(r.is_valid()); + assert.strictEqual('111', r.to_json()); + }); + it('from_json("1D2").to_human()', function() { + var r = currency.from_json("1D2"); + assert(!r.is_valid()); + assert.strictEqual('XRP', r.to_json()); + }); }); + + describe('from_human', function() { + it('From human "USD - Gold (-25%pa)"', function() { + var cur = currency.from_human('USD - Gold (-25%pa)'); + assert.strictEqual(cur.to_json(), 'USD (-25%pa)'); + assert.strictEqual(cur.to_hex(), '0155534400000000C19A22BC51297F0B00000000'); + assert.strictEqual(cur.to_json(), cur.to_human()); + }); + it('From human "EUR (-0.5%pa)', function() { + var cur = currency.from_human('EUR (-0.5%pa)'); + assert.strictEqual(cur.to_json(), 'EUR (-0.5%pa)'); + }); + it('From human "EUR (0.5361%pa)", test decimals', function() { + var cur = currency.from_human('EUR (0.5361%pa)'); + assert.strictEqual(cur.to_json(), 'EUR (0.54%pa)'); + assert.strictEqual(cur.to_json({decimals:4}), 'EUR (0.5361%pa)'); + assert.strictEqual(cur.get_interest_percentage_at(undefined, 4), 0.5361); + }); + it('From human "EUR - Euro (0.5361%pa)", test decimals and full_name', function() { + var cur = currency.from_human('EUR (0.5361%pa)'); + assert.strictEqual(cur.to_json(), 'EUR (0.54%pa)'); + assert.strictEqual(cur.to_json({decimals:4, full_name:'Euro'}), 'EUR - Euro (0.5361%pa)'); + assert.strictEqual(cur.get_interest_percentage_at(undefined, 4), 0.5361); + }); + it('From human "TYX - 30-Year Treasuries (1.5%pa)"', function() { + var cur = currency.from_human('TYX - 30-Year Treasuries (1.5%pa)'); + assert.strictEqual(cur.to_json(), 'TYX (1.5%pa)'); + }); + it('From human "TYX - 30-Year Treasuries"', function() { + var cur = currency.from_human('TYX - 30-Year Treasuries'); + assert.strictEqual(cur.to_json(), 'TYX'); + }); + it('From human "INR - Indian Rupees (-0.5%)"', function() { + var cur = currency.from_human('INR - Indian Rupees (-0.5%pa)'); + assert.strictEqual(cur.to_json(), 'INR (-0.5%pa)'); + }); + it('From human "INR - 30 Indian Rupees"', function() { + var cur = currency.from_human('INR - 30 Indian Rupees'); + assert.strictEqual(cur.to_json(), 'INR'); + }); + + }); + describe('to_human', function() { it('"USD".to_human() == "USD"', function() { assert.strictEqual('USD', currency.from_json('USD').to_human()); @@ -37,12 +90,30 @@ describe('Currency', function() { }); it('"015841551A748AD2C1F76FF6ECB0CCCD00000000") == "015841551A748AD2C1F76FF6ECB0CCCD00000000"', function() { assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human(), - 'XAU'); + 'XAU (-0.5%pa)'); + }); + it('to_human with full_name "USD - US Dollar"', function() { + assert.strictEqual('USD - US Dollar', currency.from_json('USD').to_human({full_name:'US Dollar'})); + }); + it('to_human with full_name "XRP - Ripples"', function() { + assert.strictEqual('XRP - Ripples', currency.from_json('XRP').to_human({full_name:'Ripples'})); + }); + }); + + describe('from_hex', function() { + it('"015841551A748AD2C1F76FF6ECB0CCCD00000000" === "XAU (-0.5%pa)"', function() { + var cur = currency.from_hex('015841551A748AD2C1F76FF6ECB0CCCD00000000'); + assert.strictEqual(cur.to_json(), 'XAU (-0.5%pa)'); + assert.strictEqual(cur.to_hex(), '015841551A748AD2C1F76FF6ECB0CCCD00000000'); + assert.strictEqual(cur.to_json(), cur.to_human()); }); }); describe('parse_json(currency obj)', function() { assert.strictEqual('USD', new currency().parse_json(currency.from_json('USD')).to_json()); + + assert.strictEqual('USD (0.5%pa)', new currency().parse_json(currency.from_json('USD (0.5%pa)')).to_json()); }); + describe('is_valid', function() { it('Currency.is_valid("XRP")', function() { assert(currency.is_valid('XRP'));