diff --git a/src/js/ripple/amount.js b/src/js/ripple/amount.js index d9ec80b9..b4724af3 100644 --- a/src/js/ripple/amount.js +++ b/src/js/ripple/amount.js @@ -54,7 +54,9 @@ var consts = { // Maximum possible amount for non-XRP currencies using the maximum mantissa // with maximum exponent. Corresponds to hex 0xEC6386F26FC0FFFF. - max_value: '9999999999999999e80' + max_value: '9999999999999999e80', + // Minimum possible amount for non-XRP currencies. + min_value: '-1000000000000000e-96' }; // Add constants to Amount class @@ -424,6 +426,33 @@ Amount.prototype.invert = function() { return this.copy()._invert(); }; +/** + * Canonicalize amount value + * + * Mirrors rippled's internal Amount representation + * From https://github.com/ripple/rippled/blob/develop/src/ripple/data/protocol/STAmount.h#L31-L40 + * + * Internal form: + * 1: If amount is zero, then value is zero and offset is -100 + * 2: Otherwise: + * legal offset range is -96 to +80 inclusive + * value range is 10^15 to (10^16 - 1) inclusive + * amount = value * [10 ^ offset] + * + * ------------------- + * + * The amount can be epxresses as A x 10^B + * Where: + * - A must be an integer between 10^15 and (10^16)-1 inclusive + * - B must be between -96 and 80 inclusive + * + * This results + * - minumum: 10^15 x 10^-96 -> 10^-81 -> -1e-81 + * - maximum: (10^16)-1 x 10^80 -> 9999999999999999e80 + * + * @returns {Amount} + * @throws {Error} if offset exceeds legal ranges, meaning the amount value is bigger than supported + */ Amount.prototype.canonicalize = function() { if (!(this._value instanceof BigInteger)) { // NaN. @@ -447,9 +476,8 @@ Amount.prototype.canonicalize = function() { } } - // XXX Make sure not bigger than supported. Throw if so. } else if (this.is_zero()) { - this._offset = -100; + this._offset = Amount.cMinOffset; this._is_negative = false; } else { // Normalize mantissa to valid range. @@ -465,6 +493,16 @@ Amount.prototype.canonicalize = function() { } } + // Make sure not bigger than supported. Throw if so. + if (this.is_negative() && this._offset < Amount.cMinOffset) { + throw new Error('Exceeding min value of ' + Amount.min_value); + } + + // Make sure not smaller than supported. Throw if so. + if (!this.is_negative() && this._offset > Amount.cMaxOffset) { + throw new Error('Exceeding max value of ' + Amount.max_value); + } + return this; }; @@ -539,9 +577,7 @@ Amount.prototype.equals = function(d, ignore_issuer) { return this.equals(Amount.from_json(d)); } - var result = true; - - result = !((!this.is_valid() || !d.is_valid()) + var result = !((!this.is_valid() || !d.is_valid()) || (this._is_native !== d._is_native) || (!this._value.equals(d._value) || this._offset !== d._offset) || (this._is_negative !== d._is_negative) diff --git a/test/amount-test.js b/test/amount-test.js index 58a96dc9..8c3f0da6 100644 --- a/test/amount-test.js +++ b/test/amount-test.js @@ -1162,4 +1162,80 @@ describe('Amount', function() { assert.strictEqual(demAmount.to_human_full(), '10.75853086191915/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); }); }); + + describe('amount limits', function() { + it ('max JSON wire limite', function() { + assert.strictEqual(Amount.bi_xns_max.toString(), '9000000000000000000'); + }); + + it ('max JSON wire limite', function() { + assert.strictEqual(Amount.bi_xns_min.toString(), '-9000000000000000000'); + }); + + it('max mantissa value', function() { + assert.strictEqual(Amount.bi_man_max_value.toString(), '9999999999999999'); + }); + + it('min mantissa value', function() { + assert.strictEqual(Amount.bi_man_min_value.toString(), '1000000000000000'); + }); + + it ('from_json minimum XRP', function() { + console.log('max', Amount.bi_xns_max.toString()); + var amt = Amount.from_json('-9000000000000000000'); + assert.strictEqual(amt.to_json(), '-9000000000000000000'); + }); + + it ('from_json maximum XRP', function() { + var amt = Amount.from_json('-9000000000000000000'); + assert.strictEqual(amt.to_json(), '-9000000000000000000'); + }); + + it ('from_json less than minimum XRP', function() { + var amt = Amount.from_json('-9000000000000000001'); + assert.strictEqual(amt.to_json(), '0'); + }); + + it ('from_json more than maximum XRP', function() { + var amt = Amount.from_json('9000000000000000001'); + assert.strictEqual(amt.to_json(), '0'); + }); + + it ('from_json minimum IOU', function() { + var amt = Amount.from_json('-1e-81/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); + assert.strictEqual(amt._value.toString(), Amount.bi_man_min_value.toString()); + assert.strictEqual(amt.to_text(), '-1000000000000000e-96'); + assert.strictEqual(amt.to_text(), Amount.min_value); + }); + + it('from_json exceed minimum IOU', function() { + assert.throws(function() { + Amount.from_json('-1e-82/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh') + }, 'Exceeding min value of ' + Amount.min_value); + }); + + it ('from_json maximum IOU', function() { + var amt = Amount.from_json('9999999999999999e80/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); + assert.strictEqual(amt._value.toString(), Amount.bi_man_max_value.toString()); + assert.strictEqual(amt.to_text(), '9999999999999999e80'); + }); + + it ('from_json exceed maximum IOU', function() { + assert.throws(function() { + Amount.from_json('9999999999999999e81/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh') + }, 'Exceeding max value of ' + Amount.max_value); + }); + + it ('from_json normalize mantissa to valid max range, lost significant digits', function() { + var amt = Amount.from_json('99999999999999999999999999999999/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); + assert.strictEqual(amt._value.toString(), Amount.bi_man_max_value.toString()); + assert.strictEqual(amt.to_text(), '9999999999999999e16'); + }); + + it ('from_json normalize mantissa to min valid range, lost significant digits', function() { + var amt = Amount.from_json('-0.0000000000000000000000001/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); + assert.strictEqual(amt._value.toString(), Amount.bi_man_min_value.toString()); + assert.strictEqual(amt.to_text(), '-1000000000000000e-40'); + }); + }); });