mirror of
https://github.com/Xahau/xahau.js.git
synced 2026-04-29 15:37:50 +00:00
Merge pull request #384 from mwshortt/IOU-suffix-removal
Decouple the Amount class from math performed on values
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
/* @flow */
|
||||
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const utils = require('./utils');
|
||||
@@ -29,10 +31,12 @@ function getAccountLines(remote, address, ledgerVersion, options, marker, limit,
|
||||
});
|
||||
}
|
||||
|
||||
/*:: type Options = {currency: string, counterparty: string,
|
||||
limit: number, ledgerVersion: number} */
|
||||
function getTrustlines(account: string, options: Options,
|
||||
callback: () => void): void {
|
||||
function getTrustlines(
|
||||
account: string,
|
||||
options: {currency: string, counterparty: string,
|
||||
limit: number, ledgerVersion: number},
|
||||
callback: () => void
|
||||
): void {
|
||||
validate.address(account);
|
||||
validate.options(options);
|
||||
|
||||
|
||||
@@ -3,30 +3,23 @@
|
||||
// Represent Ripple amounts and currencies.
|
||||
// - Numbers in hex are big-endian.
|
||||
|
||||
var assert = require('assert');
|
||||
var extend = require('extend');
|
||||
var utils = require('./utils');
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var Seed = require('./seed').Seed;
|
||||
var Currency = require('./currency').Currency;
|
||||
var GlobalBigNumber = require('bignumber.js');
|
||||
const assert = require('assert');
|
||||
const extend = require('extend');
|
||||
const utils = require('./utils');
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const Seed = require('./seed').Seed;
|
||||
const Currency = require('./currency').Currency;
|
||||
const Value = require('./value').Value;
|
||||
const IOUValue = require('./iouvalue').IOUValue;
|
||||
const XRPValue = require('./xrpvalue').XRPValue;
|
||||
|
||||
var BigNumber = GlobalBigNumber.another({
|
||||
ROUNDING_MODE: GlobalBigNumber.ROUND_HALF_UP,
|
||||
DECIMAL_PLACES: 40
|
||||
});
|
||||
|
||||
|
||||
function inverse(number) {
|
||||
return (new BigNumber(number)).toPower(-1);
|
||||
}
|
||||
|
||||
function Amount() {
|
||||
function Amount(value = new XRPValue(NaN)) {
|
||||
// Json format:
|
||||
// integer : XRP
|
||||
// { 'value' : ..., 'currency' : ..., 'issuer' : ...}
|
||||
assert(value instanceof Value);
|
||||
|
||||
this._value = new BigNumber(NaN);
|
||||
this._value = value;
|
||||
this._is_native = true; // Default to XRP. Only valid if value is not NaN.
|
||||
this._currency = new Currency();
|
||||
this._issuer = new UInt160();
|
||||
@@ -38,25 +31,17 @@ function Amount() {
|
||||
|
||||
Amount.strict_mode = true;
|
||||
|
||||
var consts = {
|
||||
const consts = {
|
||||
currency_xns: 0,
|
||||
currency_one: 1,
|
||||
xns_precision: 6,
|
||||
|
||||
// bi_ prefix refers to "big integer"
|
||||
// TODO: we shouldn't expose our BigNumber library publicly
|
||||
bi_5: new BigNumber(5),
|
||||
bi_7: new BigNumber(7),
|
||||
bi_10: new BigNumber(10),
|
||||
bi_1e14: new BigNumber(1e14),
|
||||
bi_1e16: new BigNumber(1e16),
|
||||
bi_1e17: new BigNumber(1e17),
|
||||
bi_1e32: new BigNumber(1e32),
|
||||
bi_man_max_value: new BigNumber('9999999999999999'),
|
||||
bi_man_min_value: new BigNumber(1e15),
|
||||
bi_xns_max: new BigNumber(1e17),
|
||||
bi_xns_min: new BigNumber(-1e17),
|
||||
bi_xns_unit: new BigNumber(1e6),
|
||||
// man refers to mantissa
|
||||
bi_man_max_value: '9999999999999999',
|
||||
bi_man_min_value: Number(1e15).toString(),
|
||||
bi_xns_max: Number(1e17).toString(),
|
||||
bi_xns_min: Number(-1e17).toString(),
|
||||
|
||||
cMinOffset: -96,
|
||||
cMaxOffset: 80,
|
||||
@@ -68,9 +53,11 @@ var consts = {
|
||||
min_value: '-1000000000000000e-96'
|
||||
};
|
||||
|
||||
var MAX_XRP_VALUE = new BigNumber(1e11);
|
||||
var MAX_IOU_VALUE = new BigNumber(consts.max_value);
|
||||
var MIN_IOU_VALUE = (new BigNumber(consts.min_value)).abs();
|
||||
const MAX_XRP_VALUE = new XRPValue(1e11);
|
||||
const MAX_IOU_VALUE = new IOUValue(consts.max_value);
|
||||
const MIN_IOU_VALUE = new IOUValue(consts.min_value).abs();
|
||||
|
||||
const bi_xns_unit = new IOUValue(1e6);
|
||||
|
||||
// Add constants to Amount class
|
||||
extend(Amount, consts);
|
||||
@@ -113,32 +100,36 @@ Amount.is_valid_full = function(j) {
|
||||
};
|
||||
|
||||
Amount.NaN = function() {
|
||||
var result = new Amount();
|
||||
result._value = new BigNumber(NaN); // should have no effect
|
||||
const result = new Amount();
|
||||
result._value = new IOUValue(NaN); // should have no effect
|
||||
return result; // but let's be careful
|
||||
};
|
||||
|
||||
// be sure that _is_native is set properly BEFORE calling _set_value
|
||||
Amount.prototype._set_value = function(value, roundingMode) {
|
||||
assert(value instanceof BigNumber);
|
||||
this._value = value.isZero() && value.isNegative() ? value.negated() : value;
|
||||
this.canonicalize(roundingMode);
|
||||
Amount.prototype._set_value = function(value: Value) {
|
||||
|
||||
this._value = value.isZero() && value.isNegative() ?
|
||||
value.negate() : value;
|
||||
this._check_limits();
|
||||
|
||||
};
|
||||
|
||||
// Returns a new value which is the absolute value of this.
|
||||
Amount.prototype.abs = function() {
|
||||
return this.clone(this.is_negative());
|
||||
|
||||
return this._copy(this._value.abs());
|
||||
|
||||
};
|
||||
|
||||
Amount.prototype.add = function(addend) {
|
||||
var addendAmount = Amount.from_json(addend);
|
||||
const addendAmount = Amount.from_json(addend);
|
||||
|
||||
if (!this.is_comparable(addendAmount)) {
|
||||
return new Amount(NaN);
|
||||
return new Amount();
|
||||
}
|
||||
|
||||
return this._copy(this._value.plus(addendAmount._value));
|
||||
return this._copy(this._value.add(addendAmount._value));
|
||||
|
||||
};
|
||||
|
||||
Amount.prototype.subtract = function(subtrahend) {
|
||||
@@ -148,34 +139,21 @@ Amount.prototype.subtract = function(subtrahend) {
|
||||
|
||||
// XXX Diverges from cpp.
|
||||
Amount.prototype.multiply = function(multiplicand) {
|
||||
var multiplicandAmount = Amount.from_json(multiplicand);
|
||||
// TODO: probably should just multiply by multiplicandAmount._value
|
||||
var multiplyBy = multiplicandAmount.is_native() ?
|
||||
multiplicandAmount._value.times(Amount.bi_xns_unit)
|
||||
: multiplicandAmount._value;
|
||||
return this._copy(this._value.times(multiplyBy));
|
||||
|
||||
const multiplicandAmount = Amount.from_json(multiplicand);
|
||||
|
||||
return this._copy(this._value.multiply(multiplicandAmount._value));
|
||||
|
||||
};
|
||||
|
||||
Amount.prototype.scale = function(scaleFactor) {
|
||||
return this._copy(this._value.times(scaleFactor));
|
||||
return this.multiply(scaleFactor);
|
||||
};
|
||||
|
||||
Amount.prototype.divide = function(divisor) {
|
||||
var divisorAmount = Amount.from_json(divisor);
|
||||
if (!this.is_valid()) {
|
||||
throw new Error('Invalid dividend');
|
||||
}
|
||||
if (!divisorAmount.is_valid()) {
|
||||
throw new Error('Invalid divisor');
|
||||
}
|
||||
if (divisorAmount.is_zero()) {
|
||||
throw new Error('divide by zero');
|
||||
}
|
||||
// TODO: probably should just divide by divisorAmount._value
|
||||
var divideBy = divisorAmount.is_native() ?
|
||||
divisorAmount._value.times(Amount.bi_xns_unit)
|
||||
: divisorAmount._value;
|
||||
return this._copy(this._value.dividedBy(divideBy));
|
||||
const divisorAmount = Amount.from_json(divisor);
|
||||
|
||||
return this._copy(this._value.divide(divisorAmount._value));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -187,7 +165,7 @@ Amount.prototype.divide = function(divisor) {
|
||||
* price would be rendered as USD.
|
||||
*
|
||||
* @example
|
||||
* var price = buy_amount.ratio_human(sell_amount);
|
||||
* const price = buy_amount.ratio_human(sell_amount);
|
||||
*
|
||||
* @this {Amount} The numerator (top half) of the fraction.
|
||||
* @param {Amount} denominator The denominator (bottom half) of the fraction.
|
||||
@@ -198,12 +176,12 @@ Amount.prototype.divide = function(divisor) {
|
||||
* @return {Amount} The resulting ratio. Unit will be the same as numerator.
|
||||
*/
|
||||
|
||||
Amount.prototype.ratio_human = function(denominator, opts) {
|
||||
opts = extend({ }, opts);
|
||||
Amount.prototype.ratio_human = function(denom, opts) {
|
||||
const options = extend({ }, opts);
|
||||
|
||||
var numerator = this.clone();
|
||||
const numerator = this.clone();
|
||||
|
||||
denominator = Amount.from_json(denominator);
|
||||
let denominator = Amount.from_json(denom);
|
||||
|
||||
// If either operand is NaN, the result is NaN.
|
||||
if (!numerator.is_valid() || !denominator.is_valid()) {
|
||||
@@ -218,8 +196,8 @@ Amount.prototype.ratio_human = function(denominator, opts) {
|
||||
//
|
||||
// We only need to apply it to the second factor, because the currency unit of
|
||||
// the first factor will carry over into the result.
|
||||
if (opts.reference_date) {
|
||||
denominator = denominator.applyInterest(opts.reference_date);
|
||||
if (options.reference_date) {
|
||||
denominator = denominator.applyInterest(options.reference_date);
|
||||
}
|
||||
|
||||
// Special case: The denominator is a native (XRP) amount.
|
||||
@@ -233,7 +211,7 @@ Amount.prototype.ratio_human = function(denominator, opts) {
|
||||
//
|
||||
// To compensate, we multiply the numerator by 10^xns_precision.
|
||||
if (denominator._is_native) {
|
||||
numerator._set_value(numerator._value.times(Amount.bi_xns_unit));
|
||||
numerator._set_value(numerator.multiply(bi_xns_unit));
|
||||
}
|
||||
|
||||
return numerator.divide(denominator);
|
||||
@@ -249,7 +227,7 @@ Amount.prototype.ratio_human = function(denominator, opts) {
|
||||
* Intended use is to calculate something like: 10 USD * 10 XRP/USD = 100 XRP
|
||||
*
|
||||
* @example
|
||||
* var sell_amount = buy_amount.product_human(price);
|
||||
* let sell_amount = buy_amount.product_human(price);
|
||||
*
|
||||
* @see Amount#ratio_human
|
||||
*
|
||||
@@ -260,32 +238,32 @@ Amount.prototype.ratio_human = function(denominator, opts) {
|
||||
* for Ripple epoch.
|
||||
* @return {Amount} The product. Unit will be the same as the first factor.
|
||||
*/
|
||||
Amount.prototype.product_human = function(factor, opts) {
|
||||
opts = opts || {};
|
||||
Amount.prototype.product_human = function(factor, options = {}) {
|
||||
|
||||
factor = Amount.from_json(factor);
|
||||
let fac = Amount.from_json(factor);
|
||||
|
||||
// If either operand is NaN, the result is NaN.
|
||||
if (!this.is_valid() || !factor.is_valid()) {
|
||||
return new Amount(NaN);
|
||||
if (!this.is_valid() || !fac.is_valid()) {
|
||||
return new Amount();
|
||||
}
|
||||
|
||||
// Apply interest/demurrage
|
||||
//
|
||||
// We only need to apply it to the second factor, because the currency unit of
|
||||
// the first factor will carry over into the result.
|
||||
if (opts.reference_date) {
|
||||
factor = factor.applyInterest(opts.reference_date);
|
||||
if (options.reference_date) {
|
||||
fac = fac.applyInterest(options.reference_date);
|
||||
}
|
||||
|
||||
var product = this.multiply(factor);
|
||||
const product = this.multiply(fac);
|
||||
|
||||
// Special case: The second factor is a native (XRP) amount expressed as base
|
||||
// units (1 XRP = 10^xns_precision base units).
|
||||
//
|
||||
// See also Amount#ratio_human.
|
||||
if (factor._is_native) {
|
||||
product._set_value(product._value.dividedBy(Amount.bi_xns_unit));
|
||||
if (fac._is_native) {
|
||||
const quotient = product.divide(bi_xns_unit.toString());
|
||||
product._set_value(quotient._value);
|
||||
}
|
||||
|
||||
return product;
|
||||
@@ -298,7 +276,7 @@ Amount.prototype.product_human = function(factor, opts) {
|
||||
* @private
|
||||
*/
|
||||
Amount.prototype._invert = function() {
|
||||
this._set_value(inverse(this._value));
|
||||
this._set_value(this._value.invert());
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -313,7 +291,7 @@ Amount.prototype.invert = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Canonicalize amount value
|
||||
* Canonicalize amount value is now taken care of in the Value classes
|
||||
*
|
||||
* Mirrors rippled's internal Amount representation
|
||||
* From https://github.com/ripple/rippled/blob/develop/src/ripple/data
|
||||
@@ -342,16 +320,6 @@ Amount.prototype.invert = function() {
|
||||
* bigger than supported
|
||||
*/
|
||||
|
||||
Amount.prototype.canonicalize = function(roundingMode) {
|
||||
if (this._is_native) {
|
||||
this._value = this._value.round(6, BigNumber.ROUND_DOWN);
|
||||
} else if (roundingMode) {
|
||||
this._value = new BigNumber(this._value.toPrecision(16, roundingMode));
|
||||
} else {
|
||||
this._value = new BigNumber(this._value.toPrecision(16));
|
||||
}
|
||||
};
|
||||
|
||||
Amount.prototype._check_limits = function() {
|
||||
if (!Amount.strict_mode) {
|
||||
return this;
|
||||
@@ -359,7 +327,7 @@ Amount.prototype._check_limits = function() {
|
||||
if (this._value.isNaN() || this._value.isZero()) {
|
||||
return this;
|
||||
}
|
||||
var absval = this._value.absoluteValue();
|
||||
const absval = this._value.abs();
|
||||
if (this._is_native) {
|
||||
if (absval.greaterThan(MAX_XRP_VALUE)) {
|
||||
throw new Error('Exceeding max value of ' + MAX_XRP_VALUE.toString());
|
||||
@@ -380,15 +348,15 @@ Amount.prototype.clone = function(negate) {
|
||||
};
|
||||
|
||||
Amount.prototype._copy = function(value) {
|
||||
var copy = this.clone();
|
||||
const copy = this.clone();
|
||||
copy._set_value(value);
|
||||
return copy;
|
||||
};
|
||||
|
||||
Amount.prototype.compareTo = function(to) {
|
||||
var toAmount = Amount.from_json(to);
|
||||
const toAmount = Amount.from_json(to);
|
||||
if (!this.is_comparable(toAmount)) {
|
||||
return new Amount(NaN);
|
||||
return new Amount();
|
||||
}
|
||||
return this._value.comparedTo(toAmount._value);
|
||||
};
|
||||
@@ -396,7 +364,7 @@ Amount.prototype.compareTo = function(to) {
|
||||
// Make d a copy of this. Returns d.
|
||||
// Modification of objects internally refered to is not allowed.
|
||||
Amount.prototype.copyTo = function(d, negate) {
|
||||
d._value = negate ? this._value.negated() : this._value;
|
||||
d._value = negate ? this._value.negate() : this._value;
|
||||
d._is_native = this._is_native;
|
||||
d._currency = this._currency;
|
||||
d._issuer = this._issuer;
|
||||
@@ -482,16 +450,16 @@ Amount.prototype.negate = function() {
|
||||
* $
|
||||
*/
|
||||
|
||||
Amount.prototype.parse_human = function(j, opts) {
|
||||
opts = opts || {};
|
||||
Amount.prototype.parse_human = function(j, options) {
|
||||
const opts = options || {};
|
||||
|
||||
var hex_RE = /^[a-fA-F0-9]{40}$/;
|
||||
var currency_RE = /^([a-zA-Z]{3}|[0-9]{3})$/;
|
||||
const hex_RE = /^[a-fA-F0-9]{40}$/;
|
||||
const currency_RE = /^([a-zA-Z]{3}|[0-9]{3})$/;
|
||||
|
||||
var value;
|
||||
var currency;
|
||||
let value;
|
||||
let currency;
|
||||
|
||||
var words = j.split(' ').filter(function(word) {
|
||||
const words = j.split(' ').filter(function(word) {
|
||||
return word !== '';
|
||||
});
|
||||
|
||||
@@ -507,7 +475,7 @@ Amount.prototype.parse_human = function(j, opts) {
|
||||
value = words[0].slice(0, -3);
|
||||
currency = words[0].slice(-3);
|
||||
if (!(isNumber(value) && currency.match(currency_RE))) {
|
||||
return new Amount(NaN);
|
||||
return new Amount();
|
||||
}
|
||||
}
|
||||
} else if (words.length === 2) {
|
||||
@@ -521,21 +489,25 @@ Amount.prototype.parse_human = function(j, opts) {
|
||||
value = words[0];
|
||||
currency = words[1];
|
||||
} else {
|
||||
return new Amount(NaN);
|
||||
return new Amount();
|
||||
}
|
||||
} else {
|
||||
return new Amount(NaN);
|
||||
return new Amount();
|
||||
}
|
||||
|
||||
currency = currency.toUpperCase();
|
||||
this.set_currency(currency);
|
||||
this._is_native = (currency === 'XRP');
|
||||
this._set_value(new BigNumber(value));
|
||||
const newValue =
|
||||
(this._is_native ? new XRPValue(value) :
|
||||
new IOUValue(value));
|
||||
this._set_value(newValue);
|
||||
|
||||
// Apply interest/demurrage
|
||||
if (opts.reference_date && this._currency.has_interest()) {
|
||||
var interest = this._currency.get_interest_at(opts.reference_date);
|
||||
this._set_value(this._value.dividedBy(interest.toString()));
|
||||
const interest = this._currency.get_interest_at(opts.reference_date);
|
||||
this._set_value(
|
||||
this._value.divide(new IOUValue(interest.toString())));
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -582,16 +554,15 @@ Amount.prototype.parse_issuer = function(issuer) {
|
||||
*/
|
||||
Amount.prototype.parse_quality =
|
||||
function(quality, counterCurrency, counterIssuer, opts) {
|
||||
opts = opts || {};
|
||||
const options = opts || {};
|
||||
|
||||
var baseCurrency = Currency.from_json(opts.base_currency);
|
||||
const baseCurrency = Currency.from_json(options.base_currency);
|
||||
|
||||
var mantissa_hex = quality.substring(quality.length - 14);
|
||||
var offset_hex = quality.substring(quality.length - 16, quality.length - 14);
|
||||
var mantissa = new BigNumber(mantissa_hex, 16);
|
||||
var offset = parseInt(offset_hex, 16) - 100;
|
||||
|
||||
var value = new BigNumber(mantissa.toString() + 'e' + offset.toString());
|
||||
const mantissa_hex = quality.substring(quality.length - 14);
|
||||
const offset_hex = quality.substring(
|
||||
quality.length - 16, quality.length - 14);
|
||||
const mantissa = new IOUValue(mantissa_hex, null, 16);
|
||||
const offset = parseInt(offset_hex, 16) - 100;
|
||||
|
||||
this._currency = Currency.from_json(counterCurrency);
|
||||
this._issuer = UInt160.from_json(counterIssuer);
|
||||
@@ -614,10 +585,11 @@ function(quality, counterCurrency, counterIssuer, opts) {
|
||||
quality as stored : 5 USD / 3000000 drops
|
||||
inverted : 3000000 drops / 5 USD
|
||||
*/
|
||||
var adjusted = opts.inverse ? inverse(value) : value;
|
||||
var nativeAdjusted = adjusted;
|
||||
const valueStr = mantissa.toString() + 'e' + offset.toString();
|
||||
let nativeAdjusted = new IOUValue(valueStr);
|
||||
nativeAdjusted = options.inverse ? nativeAdjusted.invert() : nativeAdjusted;
|
||||
|
||||
if (!opts.xrp_as_drops) {
|
||||
if (!options.xrp_as_drops) {
|
||||
// `In a currency exchange, the exchange rate is quoted as the units of the
|
||||
// counter currency in terms of a single unit of a base currency`. A
|
||||
// quality is how much taker must `pay` to get ONE `gets` unit thus:
|
||||
@@ -626,22 +598,26 @@ function(quality, counterCurrency, counterIssuer, opts) {
|
||||
if (this._is_native) {
|
||||
// pay:$price drops get:1 X
|
||||
// pay:($price / 1,000,000) XRP get:1 X
|
||||
nativeAdjusted = adjusted.div(Amount.bi_xns_unit);
|
||||
nativeAdjusted = nativeAdjusted.divide(bi_xns_unit);
|
||||
} else if (baseCurrency.is_valid() && baseCurrency.is_native()) {
|
||||
// pay:$price X get:1 drop
|
||||
// pay:($price * 1,000,000) X get:1 XRP
|
||||
nativeAdjusted = adjusted.times(Amount.bi_xns_unit);
|
||||
nativeAdjusted = nativeAdjusted.multiply(bi_xns_unit);
|
||||
}
|
||||
}
|
||||
|
||||
this._set_value(nativeAdjusted);
|
||||
|
||||
if (opts.reference_date && baseCurrency.is_valid()
|
||||
&& baseCurrency.has_interest()) {
|
||||
var interest = baseCurrency.get_interest_at(opts.reference_date);
|
||||
this._set_value(this._value.dividedBy(interest.toString()));
|
||||
if (this._is_native) {
|
||||
this._set_value(
|
||||
new XRPValue(nativeAdjusted.round(6, Value.getBNRoundDown()).toString()));
|
||||
} else {
|
||||
this._set_value(nativeAdjusted);
|
||||
}
|
||||
|
||||
if (options.reference_date && baseCurrency.is_valid()
|
||||
&& baseCurrency.has_interest()) {
|
||||
const interest = baseCurrency.get_interest_at(options.reference_date);
|
||||
this._set_value(
|
||||
this._value.divide(new IOUValue(interest.toString())));
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -649,7 +625,7 @@ Amount.prototype.parse_number = function(n) {
|
||||
this._is_native = false;
|
||||
this._currency = Currency.from_json(1);
|
||||
this._issuer = UInt160.from_json(1);
|
||||
this._set_value(new BigNumber(n));
|
||||
this._set_value(new IOUValue(n));
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -659,7 +635,7 @@ Amount.prototype.parse_json = function(j) {
|
||||
case 'string':
|
||||
// .../.../... notation is not a wire format. But allowed for easier
|
||||
// testing.
|
||||
var m = j.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
|
||||
const m = j.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
|
||||
|
||||
if (m) {
|
||||
this._currency = Currency.from_json(m[2]);
|
||||
@@ -700,7 +676,7 @@ Amount.prototype.parse_json = function(j) {
|
||||
break;
|
||||
|
||||
default:
|
||||
this._set_value(new BigNumber(NaN));
|
||||
this._set_value(new IOUValue(NaN));
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -715,11 +691,11 @@ Amount.prototype.parse_native = function(j) {
|
||||
if (j.indexOf('.') >= 0) {
|
||||
throw new Error('Native amounts must be specified in integer drops');
|
||||
}
|
||||
var value = new BigNumber(j);
|
||||
const value = new XRPValue(j);
|
||||
this._is_native = true;
|
||||
this._set_value(value.dividedBy(Amount.bi_xns_unit));
|
||||
this._set_value(value.divide(bi_xns_unit));
|
||||
} else {
|
||||
this._set_value(new BigNumber(NaN));
|
||||
this._set_value(new IOUValue(NaN));
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -729,7 +705,8 @@ Amount.prototype.parse_native = function(j) {
|
||||
// Requires _currency to be set!
|
||||
Amount.prototype.parse_value = function(j) {
|
||||
this._is_native = false;
|
||||
this._set_value(new BigNumber(j), BigNumber.ROUND_DOWN);
|
||||
const newValue = new IOUValue(j, Value.getBNRoundDown());
|
||||
this._set_value(newValue);
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -760,26 +737,27 @@ Amount.prototype.to_text = function() {
|
||||
}
|
||||
|
||||
if (this._is_native) {
|
||||
return this._value.times(Amount.bi_xns_unit).toString();
|
||||
return this._value.multiply(bi_xns_unit).toString();
|
||||
}
|
||||
|
||||
// not native
|
||||
var offset = this._value.e - 15;
|
||||
var sign = this._value.isNegative() ? '-' : '';
|
||||
var mantissa = utils.getMantissaDecimalString(this._value.absoluteValue());
|
||||
const offset = this._value.getExponent() - 15;
|
||||
const sign = this._value.isNegative() ? '-' : '';
|
||||
const mantissa = utils.getMantissa16FromString(
|
||||
this._value.abs().toString());
|
||||
if (offset !== 0 && (offset < -25 || offset > -4)) {
|
||||
// Use e notation.
|
||||
// XXX Clamp output.
|
||||
return sign + mantissa.toString() + 'e' + offset.toString();
|
||||
}
|
||||
|
||||
var val = '000000000000000000000000000'
|
||||
const val = '000000000000000000000000000'
|
||||
+ mantissa.toString()
|
||||
+ '00000000000000000000000';
|
||||
var pre = val.substring(0, offset + 43);
|
||||
var post = val.substring(offset + 43);
|
||||
var s_pre = pre.match(/[1-9].*$/); // Everything but leading zeros.
|
||||
var s_post = post.match(/[1-9]0*$/); // Last non-zero plus trailing zeros.
|
||||
const pre = val.substring(0, offset + 43);
|
||||
const post = val.substring(offset + 43);
|
||||
const s_pre = pre.match(/[1-9].*$/); // Everything but leading zeros.
|
||||
const s_post = post.match(/[1-9]0*$/); // Last non-zero plus trailing zeros.
|
||||
|
||||
return sign + (s_pre ? s_pre[0] : '0')
|
||||
+ (s_post ? '.' + post.substring(0, 1 + post.length - s_post[0].length) : '');
|
||||
@@ -801,36 +779,37 @@ Amount.prototype.applyInterest = function(referenceDate) {
|
||||
if (!this._currency.has_interest()) {
|
||||
return this;
|
||||
}
|
||||
var interest = this._currency.get_interest_at(referenceDate);
|
||||
return this._copy(this._value.times(interest.toString()));
|
||||
const interest = this._currency.get_interest_at(referenceDate);
|
||||
return this._copy(
|
||||
this._value.multiply(new IOUValue(interest.toString())));
|
||||
};
|
||||
|
||||
/**
|
||||
* Format only value in a human-readable format.
|
||||
*
|
||||
* @example
|
||||
* var pretty = amount.to_human({precision: 2});
|
||||
* let pretty = amount.to_human({precision: 2});
|
||||
*
|
||||
* @param {Object} opts Options for formatter.
|
||||
* @param {Number} opts.precision Max. number of digits after decimal point.
|
||||
* @param {Number} opts.min_precision Min. number of digits after dec. point.
|
||||
* @param {Boolean} opts.skip_empty_fraction Don't show fraction if it is zero,
|
||||
* even if min_precision is set.
|
||||
* @param {Number} opts.max_sig_digits Maximum number of significant digits.
|
||||
* @param {Object} options Options for formatter.
|
||||
* @param {Number} options.precision Max. number of digits after decimal point.
|
||||
* @param {Number} options.min_precision Min. number of digits after dec. point.
|
||||
* @param {Boolean} options.skip_empty_fraction Don't show fraction if it
|
||||
* is zero, even if min_precision is set.
|
||||
* @param {Number} options.max_sig_digits Maximum number of significant digits.
|
||||
* Will cut fractional part, but never integer part.
|
||||
* @param {Boolean|String} opts.group_sep Whether to show a separator every n
|
||||
* @param {Boolean|String} options.group_sep Whether to show a separator every n
|
||||
* digits, if a string, that value will be used as the separator. Default: ','
|
||||
* @param {Number} opts.group_width How many numbers will be grouped together,
|
||||
* default: 3.
|
||||
* @param {Boolean|String} opts.signed Whether negative numbers will have a
|
||||
* @param {Number} options.group_width How many numbers will be grouped
|
||||
* together, default: 3.
|
||||
* @param {Boolean|String} options.signed Whether negative numbers will have a
|
||||
* prefix. If String, that string will be used as the prefix. Default: '-'
|
||||
* @param {Date|Number} opts.reference_date Date based on which
|
||||
* @param {Date|Number} options.reference_date Date based on which
|
||||
* demurrage/interest should be applied. Can be given as JavaScript Date or int
|
||||
* for Ripple epoch.
|
||||
* @return {String} amount string
|
||||
*/
|
||||
Amount.prototype.to_human = function(opts) {
|
||||
opts = opts || {};
|
||||
Amount.prototype.to_human = function(options) {
|
||||
const opts = options || {};
|
||||
|
||||
if (!this.is_valid()) {
|
||||
return 'NaN';
|
||||
@@ -838,18 +817,18 @@ Amount.prototype.to_human = function(opts) {
|
||||
|
||||
/* eslint-disable consistent-this */
|
||||
// Apply demurrage/interest
|
||||
var ref = this;
|
||||
let ref = this;
|
||||
/* eslint-enable consistent-this */
|
||||
|
||||
if (opts.reference_date) {
|
||||
ref = this.applyInterest(opts.reference_date);
|
||||
}
|
||||
|
||||
var isNegative = ref._value.isNegative();
|
||||
var valueString = ref._value.abs().toFixed();
|
||||
var parts = valueString.split('.');
|
||||
var int_part = parts[0];
|
||||
var fraction_part = parts.length === 2 ? parts[1] : '';
|
||||
const isNegative = ref._value.isNegative();
|
||||
const valueString = ref._value.abs().toFixed();
|
||||
const parts = valueString.split('.');
|
||||
let int_part = parts[0];
|
||||
let fraction_part = parts.length === 2 ? parts[1] : '';
|
||||
|
||||
int_part = int_part.replace(/^0*/, '');
|
||||
fraction_part = fraction_part.replace(/0*$/, '');
|
||||
@@ -857,9 +836,9 @@ Amount.prototype.to_human = function(opts) {
|
||||
if (fraction_part.length || !opts.skip_empty_fraction) {
|
||||
// Enforce the maximum number of decimal digits (precision)
|
||||
if (typeof opts.precision === 'number') {
|
||||
var precision = Math.max(0, opts.precision);
|
||||
let precision = Math.max(0, opts.precision);
|
||||
precision = Math.min(precision, fraction_part.length);
|
||||
var rounded = Number('0.' + fraction_part).toFixed(precision);
|
||||
const rounded = Number('0.' + fraction_part).toFixed(precision);
|
||||
|
||||
if (rounded < 1) {
|
||||
fraction_part = rounded.substring(2);
|
||||
@@ -877,18 +856,18 @@ Amount.prototype.to_human = function(opts) {
|
||||
if (typeof opts.max_sig_digits === 'number') {
|
||||
// First, we count the significant digits we have.
|
||||
// A zero in the integer part does not count.
|
||||
var int_is_zero = Number(int_part) === 0;
|
||||
var digits = int_is_zero ? 0 : int_part.length;
|
||||
const int_is_zero = Number(int_part) === 0;
|
||||
let digits = int_is_zero ? 0 : int_part.length;
|
||||
|
||||
// Don't count leading zeros in the fractional part if the integer part is
|
||||
// zero.
|
||||
var sig_frac = int_is_zero
|
||||
const sig_frac = int_is_zero
|
||||
? fraction_part.replace(/^0*/, '')
|
||||
: fraction_part;
|
||||
digits += sig_frac.length;
|
||||
|
||||
// Now we calculate where we are compared to the maximum
|
||||
var rounding = digits - opts.max_sig_digits;
|
||||
let rounding = digits - opts.max_sig_digits;
|
||||
|
||||
// If we're under the maximum we want to cut no (=0) digits
|
||||
rounding = Math.max(rounding, 0);
|
||||
@@ -913,12 +892,12 @@ Amount.prototype.to_human = function(opts) {
|
||||
}
|
||||
|
||||
if (opts.group_sep !== false) {
|
||||
var sep = (typeof opts.group_sep === 'string') ? opts.group_sep : ',';
|
||||
var groups = utils.chunkString(int_part, opts.group_width || 3, true);
|
||||
const sep = (typeof opts.group_sep === 'string') ? opts.group_sep : ',';
|
||||
const groups = utils.chunkString(int_part, opts.group_width || 3, true);
|
||||
int_part = groups.join(sep);
|
||||
}
|
||||
|
||||
var formatted = '';
|
||||
let formatted = '';
|
||||
if (isNegative && opts.signed !== false) {
|
||||
formatted += '-';
|
||||
}
|
||||
@@ -929,12 +908,12 @@ Amount.prototype.to_human = function(opts) {
|
||||
return formatted;
|
||||
};
|
||||
|
||||
Amount.prototype.to_human_full = function(opts) {
|
||||
opts = opts || {};
|
||||
var value = this.to_human(opts);
|
||||
var currency = this._currency.to_human();
|
||||
var issuer = this._issuer.to_json(opts);
|
||||
var base = value + '/' + currency;
|
||||
Amount.prototype.to_human_full = function(options) {
|
||||
const opts = options || {};
|
||||
const value = this.to_human(opts);
|
||||
const currency = this._currency.to_human();
|
||||
const issuer = this._issuer.to_json(opts);
|
||||
const base = value + '/' + currency;
|
||||
return this.is_native() ? base : (base + '/' + issuer);
|
||||
};
|
||||
|
||||
@@ -943,7 +922,7 @@ Amount.prototype.to_json = function() {
|
||||
return this.to_text();
|
||||
}
|
||||
|
||||
var amount_json = {
|
||||
const amount_json = {
|
||||
value: this.to_text(),
|
||||
currency: this._currency.has_interest() ?
|
||||
this._currency.to_hex() : this._currency.to_json()
|
||||
@@ -981,8 +960,8 @@ Amount.prototype.not_equals_why = function(d, ignore_issuer) {
|
||||
return 'Native mismatch.';
|
||||
}
|
||||
|
||||
var type = this._is_native ? 'XRP' : 'Non-XRP';
|
||||
if (!this._value.isZero() && this._value.negated().equals(d._value)) {
|
||||
const type = this._is_native ? 'XRP' : 'Non-XRP';
|
||||
if (!this._value.isZero() && this._value.negate().equals(d._value)) {
|
||||
return type + ' sign differs.';
|
||||
}
|
||||
if (!this._value.equals(d._value)) {
|
||||
|
||||
56
src/core/iouvalue.js
Normal file
56
src/core/iouvalue.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/* @flow */
|
||||
|
||||
'use strict';
|
||||
|
||||
const Value = require('./value').Value;
|
||||
const XRPValue = require('./xrpvalue').XRPValue;
|
||||
const GlobalBigNumber = require('bignumber.js');
|
||||
const BigNumber = GlobalBigNumber.another({
|
||||
ROUNDING_MODE: GlobalBigNumber.ROUND_HALF_UP,
|
||||
DECIMAL_PLACES: 40
|
||||
});
|
||||
const rippleUnits = new BigNumber(1e6);
|
||||
|
||||
class IOUValue extends Value {
|
||||
|
||||
constructor(value: string | BigNumber, roundingMode: ?number = null,
|
||||
base: ?number = null) {
|
||||
|
||||
super(new BigNumber(value, base).toDigits(16, roundingMode));
|
||||
}
|
||||
|
||||
multiply(multiplicand: Value) {
|
||||
if (multiplicand instanceof XRPValue) {
|
||||
return super.multiply(
|
||||
new IOUValue(
|
||||
multiplicand._value.times(rippleUnits)));
|
||||
}
|
||||
return super.multiply(multiplicand);
|
||||
}
|
||||
|
||||
divide(divisor: Value) {
|
||||
if (divisor instanceof XRPValue) {
|
||||
return super.divide(
|
||||
new IOUValue(divisor._value.times(rippleUnits)));
|
||||
}
|
||||
return super.divide(divisor);
|
||||
}
|
||||
|
||||
negate() {
|
||||
return new IOUValue(this._value.neg());
|
||||
}
|
||||
|
||||
_canonicalize(value) {
|
||||
if (value.isNaN()) {
|
||||
throw new Error('Invalid result');
|
||||
}
|
||||
return new IOUValue(value.toPrecision(16));
|
||||
}
|
||||
|
||||
equals(comparator) {
|
||||
return (comparator instanceof IOUValue)
|
||||
&& this._value.equals(comparator._value);
|
||||
}
|
||||
}
|
||||
|
||||
exports.IOUValue = IOUValue;
|
||||
@@ -22,6 +22,7 @@ const Currency = require('./currency').Currency;
|
||||
const AutobridgeCalculator = require('./autobridgecalculator');
|
||||
const OrderBookUtils = require('./orderbookutils');
|
||||
const log = require('./log').internal.sub('orderbook');
|
||||
const IOUValue = require('./iouvalue').IOUValue;
|
||||
|
||||
function assertValidNumber(number, message) {
|
||||
assert(!_.isNull(number) && !isNaN(number), message);
|
||||
@@ -448,11 +449,9 @@ OrderBook.prototype.applyTransferRate = function(balance) {
|
||||
assert(!isNaN(balance), 'Balance is invalid');
|
||||
assertValidNumber(this._issuerTransferRate, 'Transfer rate is invalid');
|
||||
|
||||
const adjustedBalance = OrderBookUtils.normalizeAmount(balance)
|
||||
.divide(this._issuerTransferRate)
|
||||
.multiply(Amount.from_json(OrderBook.DEFAULT_TRANSFER_RATE))
|
||||
.to_json()
|
||||
.value;
|
||||
const adjustedBalance = (new IOUValue(balance))
|
||||
.divide(new IOUValue(this._issuerTransferRate))
|
||||
.multiply(new IOUValue(OrderBook.DEFAULT_TRANSFER_RATE)).toString();
|
||||
|
||||
return adjustedBalance;
|
||||
};
|
||||
|
||||
@@ -1,18 +1,50 @@
|
||||
'use strict';
|
||||
|
||||
var _ = require('lodash');
|
||||
var assert = require('assert');
|
||||
var SerializedObject = require('./serializedobject').SerializedObject;
|
||||
var Types = require('./serializedtypes');
|
||||
var Amount = require('./amount').Amount;
|
||||
|
||||
var IOU_SUFFIX = '/000/rrrrrrrrrrrrrrrrrrrrrhoLvTp';
|
||||
var OrderBookUtils = {};
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const SerializedObject = require('./serializedobject').SerializedObject;
|
||||
const Types = require('./serializedtypes');
|
||||
const Amount = require('./amount').Amount;
|
||||
const OrderBookUtils = {};
|
||||
|
||||
function assertValidNumber(number, message) {
|
||||
assert(!_.isNull(number) && !isNaN(number), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Amount from a JSON amount object using
|
||||
* passed parameters for value, currency and counterparty
|
||||
*
|
||||
* @param amount of value, currency, counterparty
|
||||
* @return JSON amount object
|
||||
*/
|
||||
|
||||
function createAmount(value, currency, counterparty) {
|
||||
const newJSON =
|
||||
{'value': value, 'currency': currency, 'issuer': counterparty};
|
||||
return Amount.from_json(newJSON);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets currency for getOfferTaker(Gets/Pays)Funded
|
||||
* @param offer
|
||||
* @return currency
|
||||
*/
|
||||
|
||||
function getCurrencyFromOffer(offer) {
|
||||
return offer.TakerPays.currency || offer.TakerGets.currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets issuer for getOfferTaker(Gets/Pays)Funded
|
||||
* @param offer
|
||||
* @return issuer
|
||||
*/
|
||||
|
||||
function getIssuerFromOffer(offer) {
|
||||
return offer.TakerPays.issuer || offer.TakerGets.issuer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts and returns offer's taker gets funded amount as a default IOU amount
|
||||
*
|
||||
@@ -23,7 +55,10 @@ function assertValidNumber(number, message) {
|
||||
OrderBookUtils.getOfferTakerGetsFunded = function(offer) {
|
||||
assertValidNumber(offer.taker_gets_funded, 'Taker gets funded is invalid');
|
||||
|
||||
return Amount.from_json(offer.taker_gets_funded + IOU_SUFFIX);
|
||||
const currency = getCurrencyFromOffer(offer);
|
||||
const issuer = getIssuerFromOffer(offer);
|
||||
|
||||
return createAmount(offer.taker_gets_funded, currency, issuer);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -36,7 +71,10 @@ OrderBookUtils.getOfferTakerGetsFunded = function(offer) {
|
||||
OrderBookUtils.getOfferTakerPaysFunded = function(offer) {
|
||||
assertValidNumber(offer.taker_pays_funded, 'Taker gets funded is invalid');
|
||||
|
||||
return Amount.from_json(offer.taker_pays_funded + IOU_SUFFIX);
|
||||
const currency = getCurrencyFromOffer(offer);
|
||||
const issuer = getIssuerFromOffer(offer);
|
||||
|
||||
return createAmount(offer.taker_pays_funded, currency, issuer);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -50,7 +88,10 @@ OrderBookUtils.getOfferTakerPaysFunded = function(offer) {
|
||||
OrderBookUtils.getOfferTakerGets = function(offer) {
|
||||
assert(typeof offer, 'object', 'Offer is invalid');
|
||||
|
||||
return Amount.from_json(offer.TakerGets + IOU_SUFFIX);
|
||||
const currency = offer.TakerPays.currency;
|
||||
const issuer = offer.TakerPays.issuer;
|
||||
|
||||
return createAmount(offer.TakerGets, currency, issuer);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -61,7 +102,7 @@ OrderBookUtils.getOfferTakerGets = function(offer) {
|
||||
*/
|
||||
|
||||
OrderBookUtils.getOfferQuality = function(offer, currencyGets) {
|
||||
var amount;
|
||||
let amount;
|
||||
|
||||
if (currencyGets.has_interest()) {
|
||||
// XXX Should use Amount#from_quality
|
||||
@@ -71,7 +112,11 @@ OrderBookUtils.getOfferQuality = function(offer, currencyGets) {
|
||||
reference_date: new Date()
|
||||
});
|
||||
} else {
|
||||
amount = Amount.from_json(offer.quality + IOU_SUFFIX);
|
||||
|
||||
const currency = getCurrencyFromOffer(offer);
|
||||
const issuer = getIssuerFromOffer(offer);
|
||||
|
||||
amount = createAmount(offer.quality, currency, issuer);
|
||||
}
|
||||
|
||||
return amount;
|
||||
@@ -89,8 +134,8 @@ OrderBookUtils.getOfferQuality = function(offer, currencyGets) {
|
||||
OrderBookUtils.convertOfferQualityToHex = function(quality) {
|
||||
assert(quality instanceof Amount, 'Quality is not an amount');
|
||||
|
||||
var so = new SerializedObject();
|
||||
Types.Quality.serialize(so, quality.to_text() + IOU_SUFFIX);
|
||||
const so = new SerializedObject();
|
||||
Types.Quality.serialize(so, quality.to_text());
|
||||
|
||||
return so.to_hex();
|
||||
};
|
||||
@@ -100,7 +145,8 @@ OrderBookUtils.convertOfferQualityToHex = function(quality) {
|
||||
*/
|
||||
|
||||
OrderBookUtils.normalizeAmount = function(value) {
|
||||
return Amount.from_json(value + IOU_SUFFIX);
|
||||
|
||||
return Amount.from_number(value);
|
||||
};
|
||||
|
||||
module.exports = OrderBookUtils;
|
||||
|
||||
@@ -8,22 +8,22 @@
|
||||
* SerializedObject.parse() or SerializedObject.serialize().
|
||||
*/
|
||||
|
||||
var assert = require('assert');
|
||||
var extend = require('extend');
|
||||
var GlobalBigNumber = require('bignumber.js');
|
||||
var Amount = require('./amount').Amount;
|
||||
var Currency = require('./currency').Currency;
|
||||
var binformat = require('./binformat');
|
||||
var utils = require('./utils');
|
||||
var sjcl = utils.sjcl;
|
||||
var SJCL_BN = sjcl.bn;
|
||||
const assert = require('assert');
|
||||
const extend = require('extend');
|
||||
const GlobalBigNumber = require('bignumber.js');
|
||||
const Amount = require('./amount').Amount;
|
||||
const Currency = require('./currency').Currency;
|
||||
const binformat = require('./binformat');
|
||||
const utils = require('./utils');
|
||||
const sjcl = utils.sjcl;
|
||||
const SJCL_BN = sjcl.bn;
|
||||
|
||||
var UInt128 = require('./uint128').UInt128;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var Base = require('./base').Base;
|
||||
const UInt128 = require('./uint128').UInt128;
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const UInt256 = require('./uint256').UInt256;
|
||||
const Base = require('./base').Base;
|
||||
|
||||
var BigNumber = GlobalBigNumber.another({
|
||||
const BigNumber = GlobalBigNumber.another({
|
||||
ROUNDING_MODE: GlobalBigNumber.ROUND_HALF_UP,
|
||||
DECIMAL_PLACES: 40
|
||||
});
|
||||
@@ -45,7 +45,7 @@ function isHexInt64String(val) {
|
||||
}
|
||||
|
||||
function serializeBits(so, bits, noLength) {
|
||||
var byteData = sjcl.codec.bytes.fromBits(bits);
|
||||
const byteData = sjcl.codec.bytes.fromBits(bits);
|
||||
if (!noLength) {
|
||||
SerializedType.serialize_varint(so, byteData.length);
|
||||
}
|
||||
@@ -68,18 +68,18 @@ function convertByteArrayToHex(byte_array) {
|
||||
}
|
||||
|
||||
function convertHexToString(hexString) {
|
||||
var bits = sjcl.codec.hex.toBits(hexString);
|
||||
const bits = sjcl.codec.hex.toBits(hexString);
|
||||
return sjcl.codec.utf8String.fromBits(bits);
|
||||
}
|
||||
|
||||
function sort_fields(keys) {
|
||||
function sort_field_compare(a, b) {
|
||||
var a_field_coordinates = binformat.fieldsInverseMap[a];
|
||||
var a_type_bits = a_field_coordinates[0];
|
||||
var a_field_bits = a_field_coordinates[1];
|
||||
var b_field_coordinates = binformat.fieldsInverseMap[b];
|
||||
var b_type_bits = b_field_coordinates[0];
|
||||
var b_field_bits = b_field_coordinates[1];
|
||||
const a_field_coordinates = binformat.fieldsInverseMap[a];
|
||||
const a_type_bits = a_field_coordinates[0];
|
||||
const a_field_bits = a_field_coordinates[1];
|
||||
const b_field_coordinates = binformat.fieldsInverseMap[b];
|
||||
const b_type_bits = b_field_coordinates[0];
|
||||
const b_field_bits = b_field_coordinates[1];
|
||||
|
||||
// Sort by type id first, then by field id
|
||||
return a_type_bits !== b_type_bits
|
||||
@@ -91,26 +91,28 @@ function sort_fields(keys) {
|
||||
}
|
||||
|
||||
SerializedType.serialize_varint = function(so, val) {
|
||||
if (val < 0) {
|
||||
let value = val;
|
||||
if (value < 0) {
|
||||
throw new Error('Variable integers are unsigned.');
|
||||
}
|
||||
|
||||
if (val <= 192) {
|
||||
so.append([val]);
|
||||
} else if (val <= 12480) {
|
||||
val -= 193;
|
||||
so.append([193 + (val >>> 8), val & 0xff]);
|
||||
} else if (val <= 918744) {
|
||||
val -= 12481;
|
||||
so.append([241 + (val >>> 16), val >>> 8 & 0xff, val & 0xff]);
|
||||
if (value <= 192) {
|
||||
so.append([value]);
|
||||
} else if (value <= 12480) {
|
||||
value -= 193;
|
||||
so.append([193 + (value >>> 8), value & 0xff]);
|
||||
} else if (value <= 918744) {
|
||||
value -= 12481;
|
||||
so.append([241 + (value >>> 16), value >>> 8 & 0xff, value & 0xff]);
|
||||
} else {
|
||||
throw new Error('Variable integer overflow.');
|
||||
}
|
||||
};
|
||||
|
||||
SerializedType.prototype.parse_varint = function(so) {
|
||||
var b1 = so.read(1)[0], b2, b3;
|
||||
var result;
|
||||
const b1 = so.read(1)[0];
|
||||
let b2, b3;
|
||||
let result;
|
||||
|
||||
if (b1 > 254) {
|
||||
throw new Error('Invalid varint length indicator');
|
||||
@@ -152,9 +154,9 @@ function convertIntegerToByteArray(val, bytes) {
|
||||
throw new Error('Value out of bounds ');
|
||||
}
|
||||
|
||||
var newBytes = [ ];
|
||||
const newBytes = [ ];
|
||||
|
||||
for (var i = 0; i < bytes; i++) {
|
||||
for (let i = 0; i < bytes; i++) {
|
||||
newBytes.unshift(val >>> (i * 8) & 0xff);
|
||||
}
|
||||
|
||||
@@ -164,14 +166,14 @@ function convertIntegerToByteArray(val, bytes) {
|
||||
// Convert a certain number of bytes from the serialized object ('so') into an
|
||||
// integer.
|
||||
function readAndSum(so, bytes) {
|
||||
var sum = 0;
|
||||
let sum = 0;
|
||||
|
||||
if (bytes > 4) {
|
||||
throw new Error('This function only supports up to four bytes.');
|
||||
}
|
||||
|
||||
for (var i = 0; i < bytes; i++) {
|
||||
var byte = so.read(1)[0];
|
||||
for (let i = 0; i < bytes; i++) {
|
||||
const byte = so.read(1)[0];
|
||||
sum += (byte << (8 * (bytes - i - 1)));
|
||||
}
|
||||
|
||||
@@ -179,7 +181,7 @@ function readAndSum(so, bytes) {
|
||||
return sum >>> 0;
|
||||
}
|
||||
|
||||
var STInt8 = exports.Int8 = new SerializedType({
|
||||
const STInt8 = exports.Int8 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
so.append(convertIntegerToByteArray(val, 1));
|
||||
},
|
||||
@@ -194,21 +196,22 @@ function serialize(so, field_name, value) {
|
||||
// so: a byte-stream to serialize into.
|
||||
// field_name: a string for the field name ('LedgerEntryType' etc.)
|
||||
// value: the value of that field.
|
||||
var field_coordinates = binformat.fieldsInverseMap[field_name];
|
||||
var type_bits = field_coordinates[0];
|
||||
var field_bits = field_coordinates[1];
|
||||
var tag_byte = (type_bits < 16
|
||||
const field_coordinates = binformat.fieldsInverseMap[field_name];
|
||||
const type_bits = field_coordinates[0];
|
||||
const field_bits = field_coordinates[1];
|
||||
const tag_byte = (type_bits < 16
|
||||
? type_bits << 4
|
||||
: 0) | (field_bits < 16
|
||||
? field_bits
|
||||
: 0);
|
||||
let val = value;
|
||||
|
||||
if (field_name === 'LedgerEntryType' && typeof value === 'string') {
|
||||
value = binformat.ledger[value][0];
|
||||
if (field_name === 'LedgerEntryType' && typeof val === 'string') {
|
||||
val = binformat.ledger[val][0];
|
||||
}
|
||||
|
||||
if (field_name === 'TransactionResult' && typeof value === 'string') {
|
||||
value = binformat.ter[value];
|
||||
if (field_name === 'TransactionResult' && typeof val === 'string') {
|
||||
val = binformat.ter[val];
|
||||
}
|
||||
|
||||
STInt8.serialize(so, tag_byte);
|
||||
@@ -222,9 +225,9 @@ function serialize(so, field_name, value) {
|
||||
}
|
||||
|
||||
// Get the serializer class (ST...)
|
||||
var serialized_object_type;
|
||||
let serialized_object_type;
|
||||
|
||||
if (field_name === 'Memo' && typeof value === 'object') {
|
||||
if (field_name === 'Memo' && typeof val === 'object') {
|
||||
// for Memo we override the default behavior with our STMemo serializer
|
||||
serialized_object_type = exports.STMemo;
|
||||
} else {
|
||||
@@ -233,7 +236,7 @@ function serialize(so, field_name, value) {
|
||||
}
|
||||
|
||||
try {
|
||||
serialized_object_type.serialize(so, value);
|
||||
serialized_object_type.serialize(so, val);
|
||||
} catch (e) {
|
||||
e.message += ' (' + field_name + ')';
|
||||
throw e;
|
||||
@@ -246,15 +249,15 @@ exports.serialize = exports.serialize_whatever = serialize;
|
||||
// parsing of that.
|
||||
|
||||
function parse(so) {
|
||||
var tag_byte = so.read(1)[0];
|
||||
var type_bits = tag_byte >> 4;
|
||||
const tag_byte = so.read(1)[0];
|
||||
let type_bits = tag_byte >> 4;
|
||||
|
||||
if (type_bits === 0) {
|
||||
type_bits = so.read(1)[0];
|
||||
}
|
||||
|
||||
var field_bits = tag_byte & 0x0f;
|
||||
var field_name = (field_bits === 0)
|
||||
const field_bits = tag_byte & 0x0f;
|
||||
let field_name = (field_bits === 0)
|
||||
? field_name = binformat.fields[type_bits][so.read(1)[0]]
|
||||
: field_name = binformat.fields[type_bits][field_bits];
|
||||
|
||||
@@ -262,7 +265,7 @@ function parse(so) {
|
||||
+ tag_byte.toString(16));
|
||||
|
||||
// Get the parser class (ST...) for a field based on the type bits.
|
||||
var type = (field_name === 'Memo')
|
||||
const type = (field_name === 'Memo')
|
||||
? exports.STMemo
|
||||
: exports[binformat.types[type_bits]];
|
||||
|
||||
@@ -273,7 +276,7 @@ function parse(so) {
|
||||
|
||||
exports.parse = exports.parse_whatever = parse;
|
||||
|
||||
var STInt16 = exports.Int16 = new SerializedType({
|
||||
const STInt16 = exports.Int16 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
so.append(convertIntegerToByteArray(val, 2));
|
||||
},
|
||||
@@ -284,7 +287,7 @@ var STInt16 = exports.Int16 = new SerializedType({
|
||||
|
||||
STInt16.id = 1;
|
||||
|
||||
var STInt32 = exports.Int32 = new SerializedType({
|
||||
const STInt32 = exports.Int32 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
so.append(convertIntegerToByteArray(val, 4));
|
||||
},
|
||||
@@ -295,42 +298,43 @@ var STInt32 = exports.Int32 = new SerializedType({
|
||||
|
||||
STInt32.id = 2;
|
||||
|
||||
var STInt64 = exports.Int64 = new SerializedType({
|
||||
const STInt64 = exports.Int64 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
var bigNumObject;
|
||||
let bigNumObject;
|
||||
let value = val;
|
||||
|
||||
if (isNumber(val)) {
|
||||
val = Math.floor(val);
|
||||
if (val < 0) {
|
||||
if (isNumber(value)) {
|
||||
value = Math.floor(value);
|
||||
if (value < 0) {
|
||||
throw new Error('Negative value for unsigned Int64 is invalid.');
|
||||
}
|
||||
bigNumObject = new SJCL_BN(val, 10);
|
||||
} else if (isString(val)) {
|
||||
if (!isHexInt64String(val)) {
|
||||
bigNumObject = new SJCL_BN(value, 10);
|
||||
} else if (isString(value)) {
|
||||
if (!isHexInt64String(value)) {
|
||||
throw new Error('Not a valid hex Int64.');
|
||||
}
|
||||
bigNumObject = new SJCL_BN(val, 16);
|
||||
} else if (val instanceof SJCL_BN) {
|
||||
if (!val.greaterEquals(0)) {
|
||||
bigNumObject = new SJCL_BN(value, 16);
|
||||
} else if (value instanceof SJCL_BN) {
|
||||
if (!value.greaterEquals(0)) {
|
||||
throw new Error('Negative value for unsigned Int64 is invalid.');
|
||||
}
|
||||
bigNumObject = val;
|
||||
bigNumObject = value;
|
||||
} else {
|
||||
throw new Error('Invalid type for Int64');
|
||||
}
|
||||
serializeBits(so, bigNumObject.toBits(64), true); // noLength = true
|
||||
},
|
||||
parse: function(so) {
|
||||
var bytes = so.read(8);
|
||||
const bytes = so.read(8);
|
||||
return SJCL_BN.fromBits(sjcl.codec.bytes.toBits(bytes));
|
||||
}
|
||||
});
|
||||
|
||||
STInt64.id = 3;
|
||||
|
||||
var STHash128 = exports.Hash128 = new SerializedType({
|
||||
const STHash128 = exports.Hash128 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
var hash = UInt128.from_json(val);
|
||||
const hash = UInt128.from_json(val);
|
||||
if (!hash.is_valid()) {
|
||||
throw new Error('Invalid Hash128');
|
||||
}
|
||||
@@ -343,9 +347,9 @@ var STHash128 = exports.Hash128 = new SerializedType({
|
||||
|
||||
STHash128.id = 4;
|
||||
|
||||
var STHash256 = exports.Hash256 = new SerializedType({
|
||||
const STHash256 = exports.Hash256 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
var hash = UInt256.from_json(val);
|
||||
const hash = UInt256.from_json(val);
|
||||
if (!hash.is_valid()) {
|
||||
throw new Error('Invalid Hash256');
|
||||
}
|
||||
@@ -358,9 +362,9 @@ var STHash256 = exports.Hash256 = new SerializedType({
|
||||
|
||||
STHash256.id = 5;
|
||||
|
||||
var STHash160 = exports.Hash160 = new SerializedType({
|
||||
const STHash160 = exports.Hash160 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
var hash = UInt160.from_json(val);
|
||||
const hash = UInt160.from_json(val);
|
||||
if (!hash.is_valid()) {
|
||||
throw new Error('Invalid Hash160');
|
||||
}
|
||||
@@ -374,9 +378,9 @@ var STHash160 = exports.Hash160 = new SerializedType({
|
||||
STHash160.id = 17;
|
||||
|
||||
// Internal
|
||||
var STCurrency = new SerializedType({
|
||||
const STCurrency = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
var currencyData = val.to_bytes();
|
||||
const currencyData = val.to_bytes();
|
||||
|
||||
if (!currencyData) {
|
||||
throw new Error(
|
||||
@@ -386,8 +390,8 @@ var STCurrency = new SerializedType({
|
||||
so.append(currencyData);
|
||||
},
|
||||
parse: function(so) {
|
||||
var bytes = so.read(20);
|
||||
var currency = Currency.from_bytes(bytes);
|
||||
const bytes = so.read(20);
|
||||
const currency = Currency.from_bytes(bytes);
|
||||
// XXX Disabled check. Theoretically, the Currency class should support any
|
||||
// UInt160 value and consider it valid. But it doesn't, so for the
|
||||
// deserialization to be usable, we need to allow invalid results for
|
||||
@@ -408,23 +412,29 @@ var STCurrency = new SerializedType({
|
||||
*/
|
||||
exports.Quality = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
var amount = Amount.from_json(val);
|
||||
let value;
|
||||
// if in format: amount/currency/issuer
|
||||
if (val.includes('/')) {
|
||||
const amount = Amount.from_json(val);
|
||||
|
||||
if (!amount.is_valid()) {
|
||||
throw new Error('Not a valid Amount object.');
|
||||
if (!amount.is_valid()) {
|
||||
throw new Error('Not a valid Amount object.');
|
||||
}
|
||||
value = new BigNumber(amount.to_text());
|
||||
} else {
|
||||
value = new BigNumber(val);
|
||||
}
|
||||
|
||||
var hi = 0, lo = 0;
|
||||
var value = new BigNumber(amount.to_text());
|
||||
var offset = value.e - 15;
|
||||
let hi = 0, lo = 0;
|
||||
|
||||
if (!amount.is_zero()) {
|
||||
const offset = value.e - 15;
|
||||
if (val !== 0) {
|
||||
// First eight bits: offset/exponent
|
||||
hi |= ((100 + offset) & 0xff) << 24;
|
||||
|
||||
// Remaining 56 bits: mantissa
|
||||
var mantissaDecimal = utils.getMantissaDecimalString(value.abs());
|
||||
var mantissaHex = (new BigNumber(mantissaDecimal)).toString(16);
|
||||
const mantissaDecimal = utils.getMantissaDecimalString(value.abs());
|
||||
const mantissaHex = (new BigNumber(mantissaDecimal)).toString(16);
|
||||
assert(mantissaHex.length <= 16,
|
||||
'Mantissa hex representation ' + mantissaHex +
|
||||
' exceeds the maximum length of 16');
|
||||
@@ -432,7 +442,7 @@ exports.Quality = new SerializedType({
|
||||
lo = parseInt(mantissaHex.slice(-8), 16);
|
||||
}
|
||||
|
||||
var valueBytes = sjcl.codec.bytes.fromBits([hi, lo]);
|
||||
const valueBytes = sjcl.codec.bytes.fromBits([hi, lo]);
|
||||
|
||||
so.append(valueBytes);
|
||||
}
|
||||
@@ -442,22 +452,22 @@ exports.Quality = new SerializedType({
|
||||
* Amount is encoded into 64 bits:
|
||||
* (1 bit non-native) (1 bit non-negative) (8 bits offset) (54 bits mantissa)
|
||||
*/
|
||||
var STAmount = exports.Amount = new SerializedType({
|
||||
const STAmount = exports.Amount = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
var amount = Amount.from_json(val);
|
||||
const amount = Amount.from_json(val);
|
||||
|
||||
if (!amount.is_valid()) {
|
||||
throw new Error('Not a valid Amount object.');
|
||||
}
|
||||
|
||||
var value = new BigNumber(amount.to_text());
|
||||
var offset = value.e - 15;
|
||||
const value = new BigNumber(amount.to_text());
|
||||
const offset = value.e - 15;
|
||||
|
||||
// Amount (64-bit integer)
|
||||
var valueBytes = utils.arraySet(8, 0);
|
||||
let valueBytes = utils.arraySet(8, 0);
|
||||
|
||||
if (amount.is_native()) {
|
||||
var valueHex = value.abs().toString(16);
|
||||
let valueHex = value.abs().toString(16);
|
||||
|
||||
if (Amount.strict_mode && value.abs().greaterThan(Amount.bi_xns_max)) {
|
||||
throw new Error('Value out of bounds');
|
||||
@@ -482,7 +492,7 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
valueBytes[0] |= 0x40;
|
||||
}
|
||||
} else {
|
||||
var hi = 0, lo = 0;
|
||||
let hi = 0, lo = 0;
|
||||
|
||||
// First bit: non-native
|
||||
hi |= 1 << 31;
|
||||
@@ -497,8 +507,8 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
hi |= ((97 + offset) & 0xff) << 22;
|
||||
|
||||
// Remaining 54 bits: mantissa
|
||||
var mantissaDecimal = utils.getMantissaDecimalString(value.abs());
|
||||
var mantissaHex = (new BigNumber(mantissaDecimal)).toString(16);
|
||||
const mantissaDecimal = utils.getMantissaDecimalString(value.abs());
|
||||
const mantissaHex = (new BigNumber(mantissaDecimal)).toString(16);
|
||||
assert(mantissaHex.length <= 16,
|
||||
'Mantissa hex representation ' + mantissaHex +
|
||||
' exceeds the maximum length of 16');
|
||||
@@ -513,7 +523,7 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
|
||||
if (!amount.is_native()) {
|
||||
// Currency (160-bit hash)
|
||||
var currency = amount.currency();
|
||||
const currency = amount.currency();
|
||||
STCurrency.serialize(so, currency, true);
|
||||
|
||||
// Issuer (160-bit hash)
|
||||
@@ -521,27 +531,28 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
}
|
||||
},
|
||||
parse: function(so) {
|
||||
var value_bytes = so.read(8);
|
||||
var is_zero = !(value_bytes[0] & 0x7f);
|
||||
const value_bytes = so.read(8);
|
||||
let is_zero = !(value_bytes[0] & 0x7f);
|
||||
|
||||
for (var i = 1; i < 8; i++) {
|
||||
for (let i = 1; i < 8; i++) {
|
||||
is_zero = is_zero && !value_bytes[i];
|
||||
}
|
||||
|
||||
var is_negative = !is_zero && !(value_bytes[0] & 0x40);
|
||||
const is_negative = !is_zero && !(value_bytes[0] & 0x40);
|
||||
|
||||
if (value_bytes[0] & 0x80) {
|
||||
// non-native
|
||||
var currency = STCurrency.parse(so);
|
||||
var issuer_bytes = so.read(20);
|
||||
var issuer = UInt160.from_bytes(issuer_bytes);
|
||||
const currency = STCurrency.parse(so);
|
||||
const issuer_bytes = so.read(20);
|
||||
const issuer = UInt160.from_bytes(issuer_bytes);
|
||||
issuer.set_version(Base.VER_ACCOUNT_ID);
|
||||
var offset = ((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6) - 97;
|
||||
var mantissa_bytes = value_bytes.slice(1);
|
||||
const offset =
|
||||
((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6) - 97;
|
||||
const mantissa_bytes = value_bytes.slice(1);
|
||||
mantissa_bytes[0] &= 0x3f;
|
||||
var mantissa = new BigNumber(utils.arrayToHex(mantissa_bytes), 16);
|
||||
var sign = is_negative ? '-' : '';
|
||||
var valueString = sign + mantissa.toString() + 'e' + offset.toString();
|
||||
const mantissa = new BigNumber(utils.arrayToHex(mantissa_bytes), 16);
|
||||
const sign = is_negative ? '-' : '';
|
||||
const valueString = sign + mantissa.toString() + 'e' + offset.toString();
|
||||
|
||||
return Amount.from_json({
|
||||
currency: currency,
|
||||
@@ -551,17 +562,17 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
}
|
||||
|
||||
// native
|
||||
var integer_bytes = value_bytes.slice();
|
||||
const integer_bytes = value_bytes.slice();
|
||||
integer_bytes[0] &= 0x3f;
|
||||
var integer_hex = utils.arrayToHex(integer_bytes);
|
||||
var value = new BigNumber(integer_hex, 16);
|
||||
const integer_hex = utils.arrayToHex(integer_bytes);
|
||||
const value = new BigNumber(integer_hex, 16);
|
||||
return Amount.from_json((is_negative ? '-' : '') + value.toString());
|
||||
}
|
||||
});
|
||||
|
||||
STAmount.id = 6;
|
||||
|
||||
var STVL = exports.VariableLength = exports.VL = new SerializedType({
|
||||
const STVL = exports.VariableLength = exports.VL = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
if (typeof val === 'string') {
|
||||
serializeHex(so, val);
|
||||
@@ -570,29 +581,29 @@ var STVL = exports.VariableLength = exports.VL = new SerializedType({
|
||||
}
|
||||
},
|
||||
parse: function(so) {
|
||||
var len = this.parse_varint(so);
|
||||
const len = this.parse_varint(so);
|
||||
return convertByteArrayToHex(so.read(len));
|
||||
}
|
||||
});
|
||||
|
||||
STVL.id = 7;
|
||||
|
||||
var STAccount = exports.Account = new SerializedType({
|
||||
const STAccount = exports.Account = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
var account = UInt160.from_json(val);
|
||||
const account = UInt160.from_json(val);
|
||||
if (!account.is_valid()) {
|
||||
throw new Error('Invalid account!');
|
||||
}
|
||||
serializeBits(so, account.to_bits());
|
||||
},
|
||||
parse: function(so) {
|
||||
var len = this.parse_varint(so);
|
||||
const len = this.parse_varint(so);
|
||||
|
||||
if (len !== 20) {
|
||||
throw new Error('Non-standard-length account ID');
|
||||
}
|
||||
|
||||
var result = UInt160.from_bytes(so.read(len));
|
||||
const result = UInt160.from_bytes(so.read(len));
|
||||
result.set_version(Base.VER_ACCOUNT_ID);
|
||||
|
||||
if (false && !result.is_valid()) {
|
||||
@@ -605,23 +616,23 @@ var STAccount = exports.Account = new SerializedType({
|
||||
|
||||
STAccount.id = 8;
|
||||
|
||||
var STPathSet = exports.PathSet = new SerializedType({
|
||||
const STPathSet = exports.PathSet = new SerializedType({
|
||||
typeBoundary: 0xff,
|
||||
typeEnd: 0x00,
|
||||
typeAccount: 0x01,
|
||||
typeCurrency: 0x10,
|
||||
typeIssuer: 0x20,
|
||||
serialize: function(so, val) {
|
||||
for (var i = 0, l = val.length; i < l; i++) {
|
||||
for (let i = 0, l = val.length; i < l; i++) {
|
||||
// Boundary
|
||||
if (i) {
|
||||
STInt8.serialize(so, this.typeBoundary);
|
||||
}
|
||||
|
||||
for (var j = 0, l2 = val[i].length; j < l2; j++) {
|
||||
var entry = val[i][j];
|
||||
for (let j = 0, l2 = val[i].length; j < l2; j++) {
|
||||
const entry = val[i][j];
|
||||
// if (entry.hasOwnProperty('_value')) {entry = entry._value;}
|
||||
var type = 0;
|
||||
let type = 0;
|
||||
|
||||
if (entry.account) {
|
||||
type |= this.typeAccount;
|
||||
@@ -640,7 +651,7 @@ var STPathSet = exports.PathSet = new SerializedType({
|
||||
}
|
||||
|
||||
if (entry.currency) {
|
||||
var currency = Currency.from_json(entry.currency, entry.non_native);
|
||||
const currency = Currency.from_json(entry.currency, entry.non_native);
|
||||
STCurrency.serialize(so, currency);
|
||||
}
|
||||
|
||||
@@ -666,9 +677,9 @@ var STPathSet = exports.PathSet = new SerializedType({
|
||||
amount, currency, issuer.
|
||||
*/
|
||||
|
||||
var path_list = [];
|
||||
var current_path = [];
|
||||
var tag_byte;
|
||||
const path_list = [];
|
||||
let current_path = [];
|
||||
let tag_byte;
|
||||
|
||||
/* eslint-disable no-cond-assign */
|
||||
|
||||
@@ -686,8 +697,8 @@ var STPathSet = exports.PathSet = new SerializedType({
|
||||
}
|
||||
|
||||
// It's an entry-begin tag.
|
||||
var entry = {};
|
||||
var type = 0;
|
||||
const entry = {};
|
||||
let type = 0;
|
||||
|
||||
if (tag_byte & this.typeAccount) {
|
||||
entry.account = STHash160.parse(so);
|
||||
@@ -729,19 +740,19 @@ var STPathSet = exports.PathSet = new SerializedType({
|
||||
|
||||
STPathSet.id = 18;
|
||||
|
||||
var STVector256 = exports.Vector256 = new SerializedType({
|
||||
const STVector256 = exports.Vector256 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
// Assume val is an array of STHash256 objects.
|
||||
SerializedType.serialize_varint(so, val.length * 32);
|
||||
for (var i = 0, l = val.length; i < l; i++) {
|
||||
for (let i = 0, l = val.length; i < l; i++) {
|
||||
STHash256.serialize(so, val[i]);
|
||||
}
|
||||
},
|
||||
parse: function(so) {
|
||||
var length = this.parse_varint(so);
|
||||
var output = [];
|
||||
const length = this.parse_varint(so);
|
||||
const output = [];
|
||||
// length is number of bytes not number of Hash256
|
||||
for (var i = 0; i < length / 32; i++) {
|
||||
for (let i = 0; i < length / 32; i++) {
|
||||
output.push(STHash256.parse(so));
|
||||
}
|
||||
return output;
|
||||
@@ -753,7 +764,7 @@ STVector256.id = 19;
|
||||
// Internal
|
||||
exports.STMemo = new SerializedType({
|
||||
serialize: function(so, val, no_marker) {
|
||||
var keys = [];
|
||||
let keys = [];
|
||||
|
||||
Object.keys(val).forEach(function(key) {
|
||||
// Ignore lowercase field names - they're non-serializable fields by
|
||||
@@ -782,37 +793,41 @@ exports.STMemo = new SerializedType({
|
||||
}
|
||||
},
|
||||
parse: function(so) {
|
||||
var output = {};
|
||||
const output = {};
|
||||
|
||||
while (so.peek(1)[0] !== 0xe1) {
|
||||
var keyval = parse(so);
|
||||
const keyval = parse(so);
|
||||
output[keyval[0]] = keyval[1];
|
||||
}
|
||||
|
||||
if (output.MemoType !== undefined) {
|
||||
try {
|
||||
var parsedType = convertHexToString(output.MemoType);
|
||||
const parsedType = convertHexToString(output.MemoType);
|
||||
|
||||
if (parsedType !== 'unformatted_memo') {
|
||||
output.parsed_memo_type = parsedType;
|
||||
}
|
||||
/*eslint-disable no-empty*/
|
||||
} catch (e) {
|
||||
// empty
|
||||
// we don't know what's in the binary, apparently it's not a UTF-8
|
||||
// string
|
||||
// this is fine, we won't add the parsed_memo_type field
|
||||
}
|
||||
/*eslint-enable no-empty*/
|
||||
}
|
||||
|
||||
if (output.MemoFormat !== undefined) {
|
||||
try {
|
||||
output.parsed_memo_format = convertHexToString(output.MemoFormat);
|
||||
/*eslint-disable no-empty*/
|
||||
} catch (e) {
|
||||
// empty
|
||||
// we don't know what's in the binary, apparently it's not a UTF-8
|
||||
// string
|
||||
// this is fine, we won't add the parsed_memo_format field
|
||||
}
|
||||
/*eslint-enable no-empty*/
|
||||
}
|
||||
|
||||
if (output.MemoData !== undefined) {
|
||||
@@ -827,6 +842,7 @@ exports.STMemo = new SerializedType({
|
||||
// otherwise see if we can parse text
|
||||
output.parsed_memo_data = convertHexToString(output.MemoData);
|
||||
}
|
||||
/*eslint-disable no-empty*/
|
||||
} catch(e) {
|
||||
// empty
|
||||
// we'll fail in case the content does not match what the MemoFormat
|
||||
@@ -834,6 +850,7 @@ exports.STMemo = new SerializedType({
|
||||
// this is fine, we won't add the parsed_memo_data, the user has to
|
||||
// parse themselves
|
||||
}
|
||||
/*eslint-enable no-empty*/
|
||||
}
|
||||
|
||||
so.read(1);
|
||||
@@ -842,9 +859,9 @@ exports.STMemo = new SerializedType({
|
||||
|
||||
});
|
||||
|
||||
var STObject = exports.Object = new SerializedType({
|
||||
const STObject = exports.Object = new SerializedType({
|
||||
serialize: function(so, val, no_marker) {
|
||||
var keys = [];
|
||||
let keys = [];
|
||||
|
||||
Object.keys(val).forEach(function(key) {
|
||||
// Ignore lowercase field names - they're non-serializable fields by
|
||||
@@ -863,7 +880,7 @@ var STObject = exports.Object = new SerializedType({
|
||||
// Sort fields
|
||||
keys = sort_fields(keys);
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
serialize(so, keys[i], val[keys[i]]);
|
||||
}
|
||||
|
||||
@@ -874,9 +891,9 @@ var STObject = exports.Object = new SerializedType({
|
||||
},
|
||||
|
||||
parse: function(so) {
|
||||
var output = {};
|
||||
const output = {};
|
||||
while (so.peek(1)[0] !== 0xe1) {
|
||||
var keyval = parse(so);
|
||||
const keyval = parse(so);
|
||||
output[keyval[0]] = keyval[1];
|
||||
}
|
||||
so.read(1);
|
||||
@@ -886,18 +903,18 @@ var STObject = exports.Object = new SerializedType({
|
||||
|
||||
STObject.id = 14;
|
||||
|
||||
var STArray = exports.Array = new SerializedType({
|
||||
const STArray = exports.Array = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
for (var i = 0, l = val.length; i < l; i++) {
|
||||
var keys = Object.keys(val[i]);
|
||||
for (let i = 0, l = val.length; i < l; i++) {
|
||||
const keys = Object.keys(val[i]);
|
||||
|
||||
if (keys.length !== 1) {
|
||||
throw new Error(
|
||||
'Cannot serialize an array containing non-single-key objects');
|
||||
}
|
||||
|
||||
var field_name = keys[0];
|
||||
var value = val[i][field_name];
|
||||
const field_name = keys[0];
|
||||
const value = val[i][field_name];
|
||||
serialize(so, field_name, value);
|
||||
}
|
||||
|
||||
@@ -906,11 +923,11 @@ var STArray = exports.Array = new SerializedType({
|
||||
},
|
||||
|
||||
parse: function(so) {
|
||||
var output = [ ];
|
||||
const output = [ ];
|
||||
|
||||
while (so.peek(1)[0] !== 0xf1) {
|
||||
var keyval = parse(so);
|
||||
var obj = { };
|
||||
const keyval = parse(so);
|
||||
const obj = { };
|
||||
obj[keyval[0]] = keyval[1];
|
||||
output.push(obj);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
function getMantissaDecimalString(bignum) {
|
||||
let mantissa = bignum.toPrecision(16)
|
||||
.replace(/\./, '') // remove decimal point
|
||||
// returns the mantissa from the passed in string,
|
||||
// adding zeros until it has 16 sd
|
||||
function getMantissa16FromString(decimalString) {
|
||||
let mantissa = decimalString.replace(/\./, '') // remove decimal point
|
||||
.replace(/e.*/, '') // remove scientific notation
|
||||
.replace(/^0*/, ''); // remove leading zeroes
|
||||
if (mantissa.length > 16) {
|
||||
return mantissa.substring(0, 16);
|
||||
}
|
||||
while (mantissa.length < 16) {
|
||||
mantissa += '0'; // add trailing zeroes until length is 16
|
||||
}
|
||||
return mantissa;
|
||||
}
|
||||
|
||||
function getMantissaDecimalString(bignum) {
|
||||
return getMantissa16FromString(bignum.toPrecision(16));
|
||||
}
|
||||
|
||||
function trace(comment, func) {
|
||||
return function() {
|
||||
console.log('%s: %s', trace, arguments.toString);
|
||||
@@ -156,6 +164,7 @@ exports.arrayUnique = arrayUnique;
|
||||
exports.toTimestamp = toTimestamp;
|
||||
exports.fromTimestamp = fromTimestamp;
|
||||
exports.getMantissaDecimalString = getMantissaDecimalString;
|
||||
exports.getMantissa16FromString = getMantissa16FromString;
|
||||
|
||||
exports.sjcl = require('sjcl-extended');
|
||||
|
||||
|
||||
109
src/core/value.js
Normal file
109
src/core/value.js
Normal file
@@ -0,0 +1,109 @@
|
||||
/* @flow */
|
||||
|
||||
'use strict';
|
||||
|
||||
const GlobalBigNumber = require('bignumber.js');
|
||||
|
||||
const BigNumber = GlobalBigNumber.another({
|
||||
ROUNDING_MODE: GlobalBigNumber.ROUND_HALF_UP,
|
||||
DECIMAL_PLACES: 40
|
||||
});
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
class Value {
|
||||
|
||||
constructor(value: string | BigNumber) {
|
||||
if (this.constructor === 'Value') {
|
||||
throw new Error(
|
||||
'Cannot instantiate Value directly, it is an abstract base class');
|
||||
}
|
||||
this._value = new BigNumber(value);
|
||||
}
|
||||
|
||||
static getBNRoundDown() {
|
||||
return BigNumber.ROUND_DOWN;
|
||||
}
|
||||
|
||||
abs() {
|
||||
const result = this._value.abs();
|
||||
return this._canonicalize(result);
|
||||
}
|
||||
|
||||
add(addend: Value) {
|
||||
assert(this.constructor === addend.constructor);
|
||||
const result = this._value.plus(addend._value);
|
||||
return this._canonicalize(result);
|
||||
}
|
||||
|
||||
subtract(subtrahend: Value) {
|
||||
assert(this.constructor === subtrahend.constructor);
|
||||
const result = this._value.minus(subtrahend._value);
|
||||
return this._canonicalize(result);
|
||||
}
|
||||
|
||||
multiply(multiplicand: Value) {
|
||||
const result = this._value.times(multiplicand._value);
|
||||
return this._canonicalize(result);
|
||||
}
|
||||
|
||||
divide(divisor: Value) {
|
||||
if (divisor.isZero()) {
|
||||
throw new Error('divide by zero');
|
||||
}
|
||||
const result = this._value.dividedBy(divisor._value);
|
||||
return this._canonicalize(result);
|
||||
}
|
||||
|
||||
invert() {
|
||||
const result = (new BigNumber(this._value)).toPower(-1);
|
||||
return this._canonicalize(result);
|
||||
}
|
||||
|
||||
round(decimalPlaces: number, roundingMode: number) {
|
||||
const result = this._value.round(decimalPlaces, roundingMode);
|
||||
return this._canonicalize(result);
|
||||
}
|
||||
|
||||
toFixed(decimalPlaces: number, roundingMode: number) {
|
||||
return this._value.toFixed(decimalPlaces, roundingMode);
|
||||
}
|
||||
|
||||
getExponent() {
|
||||
return this._value.e;
|
||||
}
|
||||
|
||||
isNaN() {
|
||||
return this._value.isNaN();
|
||||
}
|
||||
|
||||
isZero() {
|
||||
return this._value.isZero();
|
||||
}
|
||||
|
||||
isNegative() {
|
||||
return this._value.isNegative();
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this._value.toString();
|
||||
}
|
||||
|
||||
greaterThan(comparator: Value) {
|
||||
assert(this.constructor === comparator.constructor);
|
||||
return this._value.greaterThan(comparator._value);
|
||||
}
|
||||
|
||||
lessThan(comparator: Value) {
|
||||
assert(this.constructor === comparator.constructor);
|
||||
return this._value.lessThan(comparator._value);
|
||||
}
|
||||
|
||||
comparedTo(comparator: Value) {
|
||||
assert(this.constructor === comparator.constructor);
|
||||
return this._value.comparedTo(comparator._value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
exports.Value = Value;
|
||||
59
src/core/xrpvalue.js
Normal file
59
src/core/xrpvalue.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/* @flow */
|
||||
|
||||
'use strict';
|
||||
|
||||
const GlobalBigNumber = require('bignumber.js');
|
||||
const BigNumber = GlobalBigNumber.another({
|
||||
ROUNDING_MODE: GlobalBigNumber.ROUND_HALF_UP,
|
||||
DECIMAL_PLACES: 40
|
||||
});
|
||||
|
||||
const Value = require('./value').Value;
|
||||
const rippleUnits = new BigNumber(1e6);
|
||||
|
||||
class XRPValue extends Value {
|
||||
|
||||
constructor(value: string | BigNumber) {
|
||||
super(value);
|
||||
if (this._value.dp() > 6) {
|
||||
throw new Error(
|
||||
'Value has more than 6 digits of precision past the decimal point, '
|
||||
+ 'an IOUValue may be being cast to an XRPValue'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
multiply(multiplicand: Value) {
|
||||
if (multiplicand instanceof XRPValue) {
|
||||
return super.multiply(
|
||||
new XRPValue(multiplicand._value.times(rippleUnits)));
|
||||
}
|
||||
return super.multiply(multiplicand);
|
||||
}
|
||||
|
||||
divide(divisor: Value) {
|
||||
if (divisor instanceof XRPValue) {
|
||||
return super.divide(
|
||||
new XRPValue(divisor._value.times(rippleUnits)));
|
||||
}
|
||||
return super.divide(divisor);
|
||||
}
|
||||
|
||||
negate() {
|
||||
return new XRPValue(this._value.neg());
|
||||
}
|
||||
|
||||
_canonicalize(value) {
|
||||
if (value.isNaN()) {
|
||||
throw new Error('Invalid result');
|
||||
}
|
||||
return new XRPValue(value.round(6, BigNumber.ROUND_DOWN));
|
||||
}
|
||||
|
||||
equals(comparator) {
|
||||
return (comparator instanceof XRPValue)
|
||||
&& this._value.equals(comparator._value);
|
||||
}
|
||||
}
|
||||
|
||||
exports.XRPValue = XRPValue;
|
||||
@@ -1186,19 +1186,19 @@ describe('Amount', function() {
|
||||
|
||||
describe('amount limits', function() {
|
||||
it('max JSON wire limite', function() {
|
||||
assert.strictEqual(Amount.bi_xns_max.toString(), '100000000000000000');
|
||||
assert.strictEqual(Amount.bi_xns_max, '100000000000000000');
|
||||
});
|
||||
|
||||
it('max JSON wire limite', function() {
|
||||
assert.strictEqual(Amount.bi_xns_min.toString(), '-100000000000000000');
|
||||
assert.strictEqual(Amount.bi_xns_min, '-100000000000000000');
|
||||
});
|
||||
|
||||
it('max mantissa value', function() {
|
||||
assert.strictEqual(Amount.bi_man_max_value.toString(), '9999999999999999');
|
||||
assert.strictEqual(Amount.bi_man_max_value, '9999999999999999');
|
||||
});
|
||||
|
||||
it('min mantissa value', function() {
|
||||
assert.strictEqual(Amount.bi_man_min_value.toString(), '1000000000000000');
|
||||
assert.strictEqual(Amount.bi_man_min_value, '1000000000000000');
|
||||
});
|
||||
|
||||
it('from_json minimum XRP', function() {
|
||||
|
||||
Reference in New Issue
Block a user