From 30529b7a04935cc96e0bd7e90b63453432f9ae81 Mon Sep 17 00:00:00 2001 From: Madeline Shortt Date: Fri, 12 Jun 2015 09:15:31 -0700 Subject: [PATCH 1/5] Remove IOU_SUFFIX from orderbookutils.js and clean up serializedtypes.js --- src/core/orderbookutils.js | 80 +++++++-- src/core/serializedtypes.js | 317 ++++++++++++++++++------------------ 2 files changed, 227 insertions(+), 170 deletions(-) diff --git a/src/core/orderbookutils.js b/src/core/orderbookutils.js index 8ab54fae..2c9136e0 100644 --- a/src/core/orderbookutils.js +++ b/src/core/orderbookutils.js @@ -1,18 +1,35 @@ '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 JSON amount object using +* passed parameters for value, currency and counterparty +* if currency or counterparty is undefined, defaults to IOU +* +* @param amount of value, currency, counterparty +* @return JSON amount object +*/ + +function createAmount(value, currency, counterparty) { + if (currency === undefined || counterparty === undefined) { + return {'value': value, + 'currency': '000', + 'issuer': 'rrrrrrrrrrrrrrrrrrrrrhoLvTp'}; + } + return {'value': value, 'currency': currency, 'issuer': counterparty}; +} + + /** * Casts and returns offer's taker gets funded amount as a default IOU amount * @@ -23,7 +40,16 @@ 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); + let currency = offer.TakerPays.currency; + let issuer = offer.TakerPays.issuer; + + if (currency === undefined || issuer === undefined) { + currency = offer.TakerGets.currency; + issuer = offer.TakerGets.issuer; + } + + return Amount.from_json( + createAmount(offer.taker_gets_funded, currency, issuer)); }; /** @@ -36,7 +62,16 @@ 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); + let currency = offer.TakerGets.currency; + let issuer = offer.TakerGets.issuer; + + if (currency === undefined || issuer === undefined) { + currency = offer.TakerPays.currency; + issuer = offer.TakerPays.issuer; + } + + return Amount.from_json( + createAmount(offer.taker_pays_funded, currency, issuer)); }; /** @@ -50,7 +85,10 @@ OrderBookUtils.getOfferTakerPaysFunded = function(offer) { OrderBookUtils.getOfferTakerGets = function(offer) { assert(typeof offer, 'object', 'Offer is invalid'); - return Amount.from_json(offer.TakerGets + IOU_SUFFIX); + let currency = offer.TakerPays.currency; + let issuer = offer.TakerPays.issuer; + + return Amount.from_json(createAmount(offer.TakerGets, currency, issuer)); }; /** @@ -61,7 +99,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 +109,16 @@ OrderBookUtils.getOfferQuality = function(offer, currencyGets) { reference_date: new Date() }); } else { - amount = Amount.from_json(offer.quality + IOU_SUFFIX); + + let currency = offer.TakerGets.currency; + let issuer = offer.TakerGets.issuer; + + if (currency === undefined || issuer === undefined) { + currency = offer.TakerPays.currency; + issuer = offer.TakerPays.issuer; + } + + amount = Amount.from_json(createAmount(offer.quality, currency, issuer)); } return amount; @@ -89,8 +136,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); + let so = new SerializedObject(); + Types.Quality.serialize(so, quality.to_text()); return so.to_hex(); }; @@ -100,7 +147,8 @@ OrderBookUtils.convertOfferQualityToHex = function(quality) { */ OrderBookUtils.normalizeAmount = function(value) { - return Amount.from_json(value + IOU_SUFFIX); + + return Amount.from_json(createAmount(value)); }; module.exports = OrderBookUtils; diff --git a/src/core/serializedtypes.js b/src/core/serializedtypes.js index 640d90dc..44f6c851 100644 --- a/src/core/serializedtypes.js +++ b/src/core/serializedtypes.js @@ -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); + let 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); + let 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]; + let a_field_coordinates = binformat.fieldsInverseMap[a]; + let a_type_bits = a_field_coordinates[0]; + let a_field_bits = a_field_coordinates[1]; + let b_field_coordinates = binformat.fieldsInverseMap[b]; + let b_type_bits = b_field_coordinates[0]; + let 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,27 @@ 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; + let b1 = so.read(1)[0], b2, b3; + let result; if (b1 > 254) { throw new Error('Invalid varint length indicator'); @@ -152,9 +153,9 @@ function convertIntegerToByteArray(val, bytes) { throw new Error('Value out of bounds '); } - var newBytes = [ ]; + let newBytes = [ ]; - for (var i = 0; i < bytes; i++) { + for (let i = 0; i < bytes; i++) { newBytes.unshift(val >>> (i * 8) & 0xff); } @@ -164,14 +165,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++) { + let byte = so.read(1)[0]; sum += (byte << (8 * (bytes - i - 1))); } @@ -179,7 +180,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 +195,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 + let field_coordinates = binformat.fieldsInverseMap[field_name]; + let type_bits = field_coordinates[0]; + let field_bits = field_coordinates[1]; + let 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 +224,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 +235,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 +248,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; + let 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) + let 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 +264,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') + let type = (field_name === 'Memo') ? exports.STMemo : exports[binformat.types[type_bits]]; @@ -273,7 +275,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 +286,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 +297,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); + let 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); + let hash = UInt128.from_json(val); if (!hash.is_valid()) { throw new Error('Invalid Hash128'); } @@ -343,9 +346,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); + let hash = UInt256.from_json(val); if (!hash.is_valid()) { throw new Error('Invalid Hash256'); } @@ -358,9 +361,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); + let hash = UInt160.from_json(val); if (!hash.is_valid()) { throw new Error('Invalid Hash160'); } @@ -374,9 +377,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(); + let currencyData = val.to_bytes(); if (!currencyData) { throw new Error( @@ -386,8 +389,8 @@ var STCurrency = new SerializedType({ so.append(currencyData); }, parse: function(so) { - var bytes = so.read(20); - var currency = Currency.from_bytes(bytes); + let bytes = so.read(20); + let 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 +411,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('/')) { + let 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()) { + let 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); + let mantissaDecimal = utils.getMantissaDecimalString(value.abs()); + let mantissaHex = (new BigNumber(mantissaDecimal)).toString(16); assert(mantissaHex.length <= 16, 'Mantissa hex representation ' + mantissaHex + ' exceeds the maximum length of 16'); @@ -432,7 +441,7 @@ exports.Quality = new SerializedType({ lo = parseInt(mantissaHex.slice(-8), 16); } - var valueBytes = sjcl.codec.bytes.fromBits([hi, lo]); + let valueBytes = sjcl.codec.bytes.fromBits([hi, lo]); so.append(valueBytes); } @@ -442,22 +451,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); + let 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; + let value = new BigNumber(amount.to_text()); + let 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 +491,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 +506,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); + let mantissaDecimal = utils.getMantissaDecimalString(value.abs()); + let mantissaHex = (new BigNumber(mantissaDecimal)).toString(16); assert(mantissaHex.length <= 16, 'Mantissa hex representation ' + mantissaHex + ' exceeds the maximum length of 16'); @@ -513,7 +522,7 @@ var STAmount = exports.Amount = new SerializedType({ if (!amount.is_native()) { // Currency (160-bit hash) - var currency = amount.currency(); + let currency = amount.currency(); STCurrency.serialize(so, currency, true); // Issuer (160-bit hash) @@ -521,27 +530,27 @@ var STAmount = exports.Amount = new SerializedType({ } }, parse: function(so) { - var value_bytes = so.read(8); - var is_zero = !(value_bytes[0] & 0x7f); + let 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); + let 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); + let currency = STCurrency.parse(so); + let issuer_bytes = so.read(20); + let 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); + let offset = ((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6) - 97; + let 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(); + let mantissa = new BigNumber(utils.arrayToHex(mantissa_bytes), 16); + let sign = is_negative ? '-' : ''; + let valueString = sign + mantissa.toString() + 'e' + offset.toString(); return Amount.from_json({ currency: currency, @@ -551,17 +560,17 @@ var STAmount = exports.Amount = new SerializedType({ } // native - var integer_bytes = value_bytes.slice(); + let integer_bytes = value_bytes.slice(); integer_bytes[0] &= 0x3f; - var integer_hex = utils.arrayToHex(integer_bytes); - var value = new BigNumber(integer_hex, 16); + let integer_hex = utils.arrayToHex(integer_bytes); + let 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 +579,29 @@ var STVL = exports.VariableLength = exports.VL = new SerializedType({ } }, parse: function(so) { - var len = this.parse_varint(so); + let 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); + let 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); + let len = this.parse_varint(so); if (len !== 20) { throw new Error('Non-standard-length account ID'); } - var result = UInt160.from_bytes(so.read(len)); + let result = UInt160.from_bytes(so.read(len)); result.set_version(Base.VER_ACCOUNT_ID); if (false && !result.is_valid()) { @@ -605,23 +614,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++) { + let 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 +649,7 @@ var STPathSet = exports.PathSet = new SerializedType({ } if (entry.currency) { - var currency = Currency.from_json(entry.currency, entry.non_native); + let currency = Currency.from_json(entry.currency, entry.non_native); STCurrency.serialize(so, currency); } @@ -666,9 +675,9 @@ var STPathSet = exports.PathSet = new SerializedType({ amount, currency, issuer. */ - var path_list = []; - var current_path = []; - var tag_byte; + let path_list = []; + let current_path = []; + let tag_byte; /* eslint-disable no-cond-assign */ @@ -686,8 +695,8 @@ var STPathSet = exports.PathSet = new SerializedType({ } // It's an entry-begin tag. - var entry = {}; - var type = 0; + let entry = {}; + let type = 0; if (tag_byte & this.typeAccount) { entry.account = STHash160.parse(so); @@ -729,19 +738,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 = []; + let length = this.parse_varint(so); + let 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 +762,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,16 +791,16 @@ exports.STMemo = new SerializedType({ } }, parse: function(so) { - var output = {}; + let output = {}; while (so.peek(1)[0] !== 0xe1) { - var keyval = parse(so); + let keyval = parse(so); output[keyval[0]] = keyval[1]; } if (output.MemoType !== undefined) { try { - var parsedType = convertHexToString(output.MemoType); + let parsedType = convertHexToString(output.MemoType); if (parsedType !== 'unformatted_memo') { output.parsed_memo_type = parsedType; @@ -842,9 +851,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 +872,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 +883,9 @@ var STObject = exports.Object = new SerializedType({ }, parse: function(so) { - var output = {}; + let output = {}; while (so.peek(1)[0] !== 0xe1) { - var keyval = parse(so); + let keyval = parse(so); output[keyval[0]] = keyval[1]; } so.read(1); @@ -886,18 +895,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++) { + let 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]; + let field_name = keys[0]; + let value = val[i][field_name]; serialize(so, field_name, value); } @@ -906,11 +915,11 @@ var STArray = exports.Array = new SerializedType({ }, parse: function(so) { - var output = [ ]; + let output = [ ]; while (so.peek(1)[0] !== 0xf1) { - var keyval = parse(so); - var obj = { }; + let keyval = parse(so); + let obj = { }; obj[keyval[0]] = keyval[1]; output.push(obj); } From 193fcc9014a05db92dfb1ea9ab15b10ae2cf59fb Mon Sep 17 00:00:00 2001 From: Madeline Shortt Date: Mon, 22 Jun 2015 08:33:37 -0700 Subject: [PATCH 2/5] Create separate Value classes to decouple currency math and the Amount class --- src/core/amount.js | 241 +++++++++++++++++++----------------- src/core/orderbook.js | 10 +- src/core/serializedtypes.js | 8 +- src/core/value.js | 55 ++++++++ src/core/value_IOU.js | 54 ++++++++ src/core/value_XRP.js | 30 +++++ 6 files changed, 277 insertions(+), 121 deletions(-) create mode 100644 src/core/value.js create mode 100644 src/core/value_IOU.js create mode 100644 src/core/value_XRP.js diff --git a/src/core/amount.js b/src/core/amount.js index 41a41512..c8653a1b 100644 --- a/src/core/amount.js +++ b/src/core/amount.js @@ -3,15 +3,17 @@ // 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 GlobalBigNumber = require('bignumber.js'); +const Value_IOU = require('./value_IOU').Value_IOU; +const Value_XRP = require('./value_XRP').Value_XRP; -var BigNumber = GlobalBigNumber.another({ +const BigNumber = GlobalBigNumber.another({ ROUNDING_MODE: GlobalBigNumber.ROUND_HALF_UP, DECIMAL_PLACES: 40 }); @@ -38,7 +40,7 @@ function Amount() { Amount.strict_mode = true; -var consts = { +const consts = { currency_xns: 0, currency_one: 1, xns_precision: 6, @@ -68,9 +70,9 @@ 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 BigNumber(1e11); +const MAX_IOU_VALUE = new BigNumber(consts.max_value); +const MIN_IOU_VALUE = (new BigNumber(consts.min_value)).abs(); // Add constants to Amount class extend(Amount, consts); @@ -113,7 +115,7 @@ Amount.is_valid_full = function(j) { }; Amount.NaN = function() { - var result = new Amount(); + const result = new Amount(); result._value = new BigNumber(NaN); // should have no effect return result; // but let's be careful }; @@ -128,17 +130,23 @@ Amount.prototype._set_value = function(value, roundingMode) { // Returns a new value which is the absolute value of this. Amount.prototype.abs = function() { - return this.clone(this.is_negative()); + + const val = (new Value_IOU(this._value)).abs(); + return this._copy(new BigNumber(val._value)); + }; 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 this._copy(this._value.plus(addendAmount._value)); + const thisValue = new Value_IOU(this._value); + const addendValue = new Value_IOU(addendAmount._value); + return this._copy(new BigNumber(thisValue.add(addendValue)._value)); + }; Amount.prototype.subtract = function(subtrahend) { @@ -148,20 +156,22 @@ 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); + const multiplyBy = multiplicandAmount.is_native() ? + new Value_XRP(multiplicandAmount._value, Amount.bi_xns_unit) + : new Value_IOU(multiplicandAmount._value); + const thisValue = new Value_IOU(this._value); + return this._copy(new BigNumber(thisValue.multiply(multiplyBy)._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); + const divisorAmount = Amount.from_json(divisor); if (!this.is_valid()) { throw new Error('Invalid dividend'); } @@ -171,11 +181,11 @@ Amount.prototype.divide = function(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 divideBy = divisorAmount.is_native() ? + new Value_XRP(divisorAmount._value, Amount.bi_xns_unit) + : new Value_IOU(divisorAmount._value); + const thisValue = new Value_IOU(this._value); + return this._copy(new BigNumber(thisValue.divide(divideBy)._value)); }; /** @@ -187,7 +197,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 +208,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 +228,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. @@ -249,21 +259,21 @@ 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 * - * @param {Amount} factor The second factor of the product. + * @param {Amount} fac The second factor of the product. * @param {Object} opts Options for the calculation. * @param {Date|Number} opts.reference_date Date based on which * demurrage/interest should be applied. Can be given as JavaScript Date or int * 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(fac, opts) { + const options = opts || {}; - factor = Amount.from_json(factor); + let factor = Amount.from_json(fac); // If either operand is NaN, the result is NaN. if (!this.is_valid() || !factor.is_valid()) { @@ -274,11 +284,11 @@ Amount.prototype.product_human = function(factor, 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) { - factor = factor.applyInterest(opts.reference_date); + if (options.reference_date) { + factor = factor.applyInterest(options.reference_date); } - var product = this.multiply(factor); + const product = this.multiply(factor); // Special case: The second factor is a native (XRP) amount expressed as base // units (1 XRP = 10^xns_precision base units). @@ -359,7 +369,7 @@ Amount.prototype._check_limits = function() { if (this._value.isNaN() || this._value.isZero()) { return this; } - var absval = this._value.absoluteValue(); + const absval = this._value.absoluteValue(); if (this._is_native) { if (absval.greaterThan(MAX_XRP_VALUE)) { throw new Error('Exceeding max value of ' + MAX_XRP_VALUE.toString()); @@ -380,13 +390,13 @@ 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); } @@ -482,16 +492,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 !== ''; }); @@ -534,7 +544,7 @@ Amount.prototype.parse_human = function(j, opts) { // Apply interest/demurrage if (opts.reference_date && this._currency.has_interest()) { - var interest = this._currency.get_interest_at(opts.reference_date); + const interest = this._currency.get_interest_at(opts.reference_date); this._set_value(this._value.dividedBy(interest.toString())); } @@ -582,16 +592,17 @@ 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; + const mantissa_hex = quality.substring(quality.length - 14); + const offset_hex = quality.substring( + quality.length - 16, quality.length - 14); + const mantissa = new BigNumber(mantissa_hex, 16); + const offset = parseInt(offset_hex, 16) - 100; - var value = new BigNumber(mantissa.toString() + 'e' + offset.toString()); + const value = new BigNumber(mantissa.toString() + 'e' + offset.toString()); this._currency = Currency.from_json(counterCurrency); this._issuer = UInt160.from_json(counterIssuer); @@ -614,10 +625,10 @@ 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 adjusted = options.inverse ? inverse(value) : value; + let nativeAdjusted = adjusted; - 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: @@ -636,9 +647,9 @@ function(quality, counterCurrency, counterIssuer, opts) { this._set_value(nativeAdjusted); - if (opts.reference_date && baseCurrency.is_valid() + if (options.reference_date && baseCurrency.is_valid() && baseCurrency.has_interest()) { - var interest = baseCurrency.get_interest_at(opts.reference_date); + const interest = baseCurrency.get_interest_at(options.reference_date); this._set_value(this._value.dividedBy(interest.toString())); } @@ -659,7 +670,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]); @@ -715,7 +726,7 @@ 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 BigNumber(j); this._is_native = true; this._set_value(value.dividedBy(Amount.bi_xns_unit)); } else { @@ -764,22 +775,22 @@ Amount.prototype.to_text = function() { } // not native - var offset = this._value.e - 15; - var sign = this._value.isNegative() ? '-' : ''; - var mantissa = utils.getMantissaDecimalString(this._value.absoluteValue()); + const offset = this._value.e - 15; + const sign = this._value.isNegative() ? '-' : ''; + const mantissa = utils.getMantissaDecimalString(this._value.absoluteValue()); 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,7 +812,7 @@ Amount.prototype.applyInterest = function(referenceDate) { if (!this._currency.has_interest()) { return this; } - var interest = this._currency.get_interest_at(referenceDate); + const interest = this._currency.get_interest_at(referenceDate); return this._copy(this._value.times(interest.toString())); }; @@ -809,28 +820,28 @@ Amount.prototype.applyInterest = function(referenceDate) { * 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 +849,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 +868,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 +888,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 +924,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 +940,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 +954,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,7 +992,7 @@ Amount.prototype.not_equals_why = function(d, ignore_issuer) { return 'Native mismatch.'; } - var type = this._is_native ? 'XRP' : 'Non-XRP'; + let type = this._is_native ? 'XRP' : 'Non-XRP'; if (!this._value.isZero() && this._value.negated().equals(d._value)) { return type + ' sign differs.'; } diff --git a/src/core/orderbook.js b/src/core/orderbook.js index 400318e8..0a1ef77f 100644 --- a/src/core/orderbook.js +++ b/src/core/orderbook.js @@ -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 Value_IOU = require('./value_IOU').Value_IOU; function assertValidNumber(number, message) { assert(!_.isNull(number) && !isNaN(number), message); @@ -448,11 +449,10 @@ 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 Value_IOU(balance)) + .divide(new Value_IOU(this._issuerTransferRate)) + .multiply(new Value_IOU(OrderBook.DEFAULT_TRANSFER_RATE)) + ._value.toString(); return adjustedBalance; }; diff --git a/src/core/serializedtypes.js b/src/core/serializedtypes.js index 44f6c851..022f39a7 100644 --- a/src/core/serializedtypes.js +++ b/src/core/serializedtypes.js @@ -441,7 +441,7 @@ exports.Quality = new SerializedType({ lo = parseInt(mantissaHex.slice(-8), 16); } - let valueBytes = sjcl.codec.bytes.fromBits([hi, lo]); + const valueBytes = sjcl.codec.bytes.fromBits([hi, lo]); so.append(valueBytes); } @@ -805,23 +805,27 @@ exports.STMemo = new SerializedType({ 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) { @@ -836,6 +840,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 @@ -843,6 +848,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); diff --git a/src/core/value.js b/src/core/value.js new file mode 100644 index 00000000..ee179c4c --- /dev/null +++ b/src/core/value.js @@ -0,0 +1,55 @@ +'use strict'; + +const GlobalBigNumber = require('bignumber.js'); + +const BigNumber = GlobalBigNumber.another({ + ROUNDING_MODE: GlobalBigNumber.ROUND_HALF_UP, + DECIMAL_PLACES: 40 +}); + +function Value(value) { + this._value = new BigNumber(value); +} + +Value.prototype.abs = function() { + let result = this._value.abs(); + return this._canonicalize(result); +}; + +Value.prototype.add = function(addend) { + let result = this._value.plus(addend._value); + return this._canonicalize(result); +}; + +Value.prototype.subtract = function(subtrahend) { + let result = this._value.minus(subtrahend._value); + return this._canonicalize(result); +}; + +Value.prototype.multiply = function(multiplicand) { + let val = this._value; + let mult = multiplicand._value; + let result = (val).times(mult); + return this._canonicalize(result); +}; + +Value.prototype.scale = function(scaleFactor) { + let result = this._value.times(scaleFactor._value); + return this._canonicalize(result); + +}; + +Value.prototype.divide = function(divisor) { + if (divisor === 0) { + throw new Error('Divide by zero'); + } + let result = this._value.dividedBy(divisor._value); + return this._canonicalize(result); +}; + +Value.prototype.invert = function() { + let result = (new BigNumber(this._value)).toPower(-1); + return this._canonicalize(result); +}; + +exports.Value = Value; diff --git a/src/core/value_IOU.js b/src/core/value_IOU.js new file mode 100644 index 00000000..2d32b016 --- /dev/null +++ b/src/core/value_IOU.js @@ -0,0 +1,54 @@ +'use strict'; + +const Value = require('./value').Value; +const GlobalBigNumber = require('bignumber.js'); +const BigNumber = GlobalBigNumber.another({ + ROUNDING_MODE: GlobalBigNumber.ROUND_HALF_UP, + DECIMAL_PLACES: 40 +}); + +function Value_IOU(value) { + Value.call(this, value); +} + +Value_IOU.prototype = Object.create(Value.prototype); + +Value_IOU.prototype.constructor = Value_IOU; + +Value_IOU.fromXRPValue = function(XRPvalue, bi_xns_unit) { + const newV = new Value_IOU(XRPvalue); + newV._XRP = true; + newV._bi_xns_unit = bi_xns_unit; + return newV; +}; + +Value_IOU.prototype.multiply = function(multiplicand) { + let mult = multiplicand; + if (mult._XRP) { + let constant = new BigNumber((mult._bi_xns_unit).toString()); + let value = new BigNumber(mult._value); + mult._value = (value).times(constant); + } + return Value.prototype.multiply.call(this, mult); +}; + +Value_IOU.prototype.divide = function(divisor) { + let div = divisor; + if (div._XRP) { + let constant = new BigNumber((div._bi_xns_unit).toString()); + let value = new BigNumber(div._value); + div._value = (value).times(constant); + } + return Value.prototype.divide.call(this, div); +}; + +Value_IOU.prototype._canonicalize = function(value) { + return new Value_IOU(value.toPrecision(16)); +}; + +Value_IOU.prototype.equals = function(comparator) { + return (comparator instanceof Value_IOU) + && this._value.equals(comparator.value); +}; + +exports.Value_IOU = Value_IOU; diff --git a/src/core/value_XRP.js b/src/core/value_XRP.js new file mode 100644 index 00000000..3da35a6b --- /dev/null +++ b/src/core/value_XRP.js @@ -0,0 +1,30 @@ +'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; + +function Value_XRP(value, bi_xns_unit) { + Value.call(this, value); + this._bi_xns_unit = bi_xns_unit; + this._XRP = true; +} + +Value_XRP.prototype = Object.create(Value.prototype); + +Value_XRP.prototype.constructor = Value_XRP; + +Value_XRP.prototype._canonicalize = function(value) { + return new Value_XRP(value.round(6, BigNumber.ROUND_DOWN)); +}; + +Value_XRP.prototype.equals = function(comparator) { + return (comparator instanceof Value_XRP) + && this._value.equals(comparator.value); +}; + +exports.Value_XRP = Value_XRP; From 13e9ad45f97c91b8b6d4156af9f3959dce4ae3f4 Mon Sep 17 00:00:00 2001 From: Madeline Shortt Date: Tue, 30 Jun 2015 17:26:42 -0700 Subject: [PATCH 3/5] Remove BigNumber completely from Amount and small changes --- src/api/ledger/trustlines.js | 12 ++- src/core/IOUValue.js | 43 +++++++++ src/core/XRPValue.js | 29 ++++++ src/core/amount.js | 166 +++++++++++++++++++---------------- src/core/orderbook.js | 8 +- src/core/orderbookutils.js | 86 ++++++++++-------- src/core/serializedtypes.js | 140 ++++++++++++++--------------- src/core/value.js | 118 ++++++++++++++++--------- src/core/value_IOU.js | 54 ------------ src/core/value_XRP.js | 30 ------- 10 files changed, 366 insertions(+), 320 deletions(-) create mode 100644 src/core/IOUValue.js create mode 100644 src/core/XRPValue.js delete mode 100644 src/core/value_IOU.js delete mode 100644 src/core/value_XRP.js diff --git a/src/api/ledger/trustlines.js b/src/api/ledger/trustlines.js index 816b7067..4fd67e27 100644 --- a/src/api/ledger/trustlines.js +++ b/src/api/ledger/trustlines.js @@ -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); diff --git a/src/core/IOUValue.js b/src/core/IOUValue.js new file mode 100644 index 00000000..3ae3e242 --- /dev/null +++ b/src/core/IOUValue.js @@ -0,0 +1,43 @@ +/* @flow */ + +'use strict'; + +const Value = require('./value').Value; +const GlobalBigNumber = require('bignumber.js'); +const BigNumber = GlobalBigNumber.another({ + ROUNDING_MODE: GlobalBigNumber.ROUND_HALF_UP, + DECIMAL_PLACES: 40 +}); + +class IOUValue extends Value { + + constructor(value: string | BigNumber) { + super(value); + } + + multiplyByXRP(multiplicand: {_value: BigNumber, _bi_xns_unit: number}) { + const constant = new BigNumber((multiplicand._bi_xns_unit).toString()); + const value = new BigNumber(multiplicand._value); + multiplicand._value = (value).times(constant); + return super.multiply(multiplicand); + } + + divideByXRP(divisor: {_value: BigNumber, _bi_xns_unit: number}) { + const constant = new BigNumber((divisor._bi_xns_unit).toString()); + const value = new BigNumber(divisor._value); + divisor._value = (value).times(constant); + return super.divide(divisor); + } + + _canonicalize(value) { + return new IOUValue(value.toPrecision(16)); + } + + equals(comparator) { + return (comparator instanceof IOUValue) + && this._value.equals(comparator.value); + } + +} + +exports.IOUValue = IOUValue; diff --git a/src/core/XRPValue.js b/src/core/XRPValue.js new file mode 100644 index 00000000..f68ec198 --- /dev/null +++ b/src/core/XRPValue.js @@ -0,0 +1,29 @@ +'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; + +class XRPValue extends Value { + + constructor(value, bi_xns_unit) { + super(value); + this._bi_xns_unit = bi_xns_unit; + } + + _canonicalize(value) { + return new XRPValue(value.round(6, BigNumber.ROUND_DOWN)); + } + + equals(comparator) { + return (comparator instanceof XRPValue) + && this._value.equals(comparator.value); + } + +} + +exports.XRPValue = XRPValue; diff --git a/src/core/amount.js b/src/core/amount.js index c8653a1b..5797feb2 100644 --- a/src/core/amount.js +++ b/src/core/amount.js @@ -9,18 +9,13 @@ const utils = require('./utils'); const UInt160 = require('./uint160').UInt160; const Seed = require('./seed').Seed; const Currency = require('./currency').Currency; -const GlobalBigNumber = require('bignumber.js'); -const Value_IOU = require('./value_IOU').Value_IOU; -const Value_XRP = require('./value_XRP').Value_XRP; - -const BigNumber = GlobalBigNumber.another({ - ROUNDING_MODE: GlobalBigNumber.ROUND_HALF_UP, - DECIMAL_PLACES: 40 -}); +const Value = require('./value').Value; +const IOUValue = require('./IOUValue').IOUValue; +const XRPValue = require('./XRPValue').XRPValue; function inverse(number) { - return (new BigNumber(number)).toPower(-1); + return new IOUValue(number).invert()._value; } function Amount() { @@ -28,7 +23,7 @@ function Amount() { // integer : XRP // { 'value' : ..., 'currency' : ..., 'issuer' : ...} - this._value = new BigNumber(NaN); + this._value = new Value(NaN)._value; this._is_native = true; // Default to XRP. Only valid if value is not NaN. this._currency = new Currency(); this._issuer = new UInt160(); @@ -47,18 +42,18 @@ const consts = { // 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), + bi_5: new Value(5)._value, + bi_7: new Value(7)._value, + bi_10: new Value(10)._value, + bi_1e14: new Value(1e14)._value, + bi_1e16: new Value(1e16)._value, + bi_1e17: new Value(1e17)._value, + bi_1e32: new Value(1e32)._value, + bi_man_max_value: new Value('9999999999999999')._value, + bi_man_min_value: new Value(1e15)._value, + bi_xns_max: new Value(1e17)._value, + bi_xns_min: new Value(-1e17)._value, + bi_xns_unit: new Value(1e6)._value, cMinOffset: -96, cMaxOffset: 80, @@ -70,9 +65,9 @@ const consts = { min_value: '-1000000000000000e-96' }; -const MAX_XRP_VALUE = new BigNumber(1e11); -const MAX_IOU_VALUE = new BigNumber(consts.max_value); -const MIN_IOU_VALUE = (new BigNumber(consts.min_value)).abs(); +const MAX_XRP_VALUE = new Value(1e11)._value; +const MAX_IOU_VALUE = new Value(consts.max_value)._value; +const MIN_IOU_VALUE = new Value(consts.min_value)._value.abs(); // Add constants to Amount class extend(Amount, consts); @@ -116,23 +111,30 @@ Amount.is_valid_full = function(j) { Amount.NaN = function() { const result = new Amount(); - result._value = new BigNumber(NaN); // should have no effect + result._value = new Value(NaN)._value; // 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; + assert(value instanceof Object); + + this._value = this._value = value.isZero() && value.isNegative() ? + value.negated()._value : value._value; + if (!this._value) { + this._value = value.isZero() && value.isNegative() ? + value.negated() : value; + } this.canonicalize(roundingMode); this._check_limits(); + }; // Returns a new value which is the absolute value of this. Amount.prototype.abs = function() { - const val = (new Value_IOU(this._value)).abs(); - return this._copy(new BigNumber(val._value)); + const val = (new IOUValue(this._value)).abs(); + return this._copy(val); }; @@ -143,9 +145,9 @@ Amount.prototype.add = function(addend) { return new Amount(NaN); } - const thisValue = new Value_IOU(this._value); - const addendValue = new Value_IOU(addendAmount._value); - return this._copy(new BigNumber(thisValue.add(addendValue)._value)); + const thisValue = new IOUValue(this._value); + const addendValue = new IOUValue(addendAmount._value); + return this._copy(thisValue.add(addendValue)); }; @@ -158,11 +160,17 @@ Amount.prototype.subtract = function(subtrahend) { Amount.prototype.multiply = function(multiplicand) { const multiplicandAmount = Amount.from_json(multiplicand); - const multiplyBy = multiplicandAmount.is_native() ? - new Value_XRP(multiplicandAmount._value, Amount.bi_xns_unit) - : new Value_IOU(multiplicandAmount._value); - const thisValue = new Value_IOU(this._value); - return this._copy(new BigNumber(thisValue.multiply(multiplyBy)._value)); + const thisValue = new IOUValue(this._value); + + let multiplyBy; + + if (multiplicandAmount.is_native()) { + multiplyBy = new XRPValue(multiplicandAmount._value, Amount.bi_xns_unit); + return this._copy(thisValue.multiplyByXRP(multiplyBy)._value); + } + + multiplyBy = new IOUValue(multiplicandAmount._value); + return this._copy(thisValue.multiply(multiplyBy)); }; @@ -172,20 +180,17 @@ Amount.prototype.scale = function(scaleFactor) { Amount.prototype.divide = function(divisor) { const divisorAmount = Amount.from_json(divisor); - if (!this.is_valid()) { - throw new Error('Invalid dividend'); + const thisValue = new IOUValue(this._value); + + let divideBy; + + if (divisorAmount.is_native()) { + divideBy = new XRPValue(divisorAmount._value, Amount.bi_xns_unit); + return this._copy(thisValue.divideByXRP(divideBy)._value); } - if (!divisorAmount.is_valid()) { - throw new Error('Invalid divisor'); - } - if (divisorAmount.is_zero()) { - throw new Error('divide by zero'); - } - const divideBy = divisorAmount.is_native() ? - new Value_XRP(divisorAmount._value, Amount.bi_xns_unit) - : new Value_IOU(divisorAmount._value); - const thisValue = new Value_IOU(this._value); - return this._copy(new BigNumber(thisValue.divide(divideBy)._value)); + + divideBy = new IOUValue(divisorAmount._value); + return this._copy(thisValue.divide(divideBy)); }; /** @@ -243,7 +248,7 @@ Amount.prototype.ratio_human = function(denom, 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(Amount.bi_xns_unit)); } return numerator.divide(denominator); @@ -270,8 +275,7 @@ Amount.prototype.ratio_human = function(denom, opts) { * for Ripple epoch. * @return {Amount} The product. Unit will be the same as the first factor. */ -Amount.prototype.product_human = function(fac, opts) { - const options = opts || {}; +Amount.prototype.product_human = function(fac, options = {}) { let factor = Amount.from_json(fac); @@ -295,7 +299,8 @@ Amount.prototype.product_human = function(fac, opts) { // // See also Amount#ratio_human. if (factor._is_native) { - product._set_value(product._value.dividedBy(Amount.bi_xns_unit)); + const quotient = product.divide(Amount.bi_xns_unit.toString()); + product._set_value(quotient._value); } return product; @@ -353,12 +358,15 @@ Amount.prototype.invert = function() { */ Amount.prototype.canonicalize = function(roundingMode) { + if (typeof this._value === 'undefined') { + throw new Error('undefined value'); + } if (this._is_native) { - this._value = this._value.round(6, BigNumber.ROUND_DOWN); + this._value = this._value.round(6, Value.getBNRoundDown()); } else if (roundingMode) { - this._value = new BigNumber(this._value.toPrecision(16, roundingMode)); + this._value = new Value(this._value.toPrecision(16, roundingMode))._value; } else { - this._value = new BigNumber(this._value.toPrecision(16)); + this._value = new Value(this._value.toPrecision(16))._value; } }; @@ -540,12 +548,13 @@ Amount.prototype.parse_human = function(j, options) { currency = currency.toUpperCase(); this.set_currency(currency); this._is_native = (currency === 'XRP'); - this._set_value(new BigNumber(value)); + this._set_value(new Value(value)._value); // Apply interest/demurrage if (opts.reference_date && this._currency.has_interest()) { const interest = this._currency.get_interest_at(opts.reference_date); - this._set_value(this._value.dividedBy(interest.toString())); + this._set_value( + new IOUValue(this._value).divide(new Value(interest.toString()))); } return this; @@ -599,11 +608,11 @@ function(quality, counterCurrency, counterIssuer, opts) { const mantissa_hex = quality.substring(quality.length - 14); const offset_hex = quality.substring( quality.length - 16, quality.length - 14); - const mantissa = new BigNumber(mantissa_hex, 16); + const mantissa = new Value(mantissa_hex, 16)._value; const offset = parseInt(offset_hex, 16) - 100; - const value = new BigNumber(mantissa.toString() + 'e' + offset.toString()); - + const valueStr = mantissa.toString() + 'e' + offset.toString(); + const value = new Value(valueStr)._value; this._currency = Currency.from_json(counterCurrency); this._issuer = UInt160.from_json(counterIssuer); this._is_native = this._currency.is_native(); @@ -626,7 +635,8 @@ function(quality, counterCurrency, counterIssuer, opts) { inverted : 3000000 drops / 5 USD */ const adjusted = options.inverse ? inverse(value) : value; - let nativeAdjusted = adjusted; + let nativeAdjusted = this._is_native ? + new XRPValue(adjusted, Amount.bi_xns_unit) : new IOUValue(adjusted); if (!options.xrp_as_drops) { // `In a currency exchange, the exchange rate is quoted as the units of the @@ -637,20 +647,20 @@ 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(new Value(Amount.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(new Value(Amount.bi_xns_unit)); } } - 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.dividedBy(interest.toString())); + this._set_value( + new IOUValue(this._value).divide(new Value(interest.toString()))); } return this; @@ -660,7 +670,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 Value(n)._value); return this; }; @@ -711,7 +721,7 @@ Amount.prototype.parse_json = function(j) { break; default: - this._set_value(new BigNumber(NaN)); + this._set_value(new Value(NaN)._value); } return this; @@ -726,11 +736,11 @@ Amount.prototype.parse_native = function(j) { if (j.indexOf('.') >= 0) { throw new Error('Native amounts must be specified in integer drops'); } - const value = new BigNumber(j); + const value = new XRPValue(j, Amount.bi_xns_unit); this._is_native = true; - this._set_value(value.dividedBy(Amount.bi_xns_unit)); + this._set_value(value.divide(new Value(Amount.bi_xns_unit))); } else { - this._set_value(new BigNumber(NaN)); + this._set_value(new Value(NaN)._value); } return this; @@ -740,7 +750,7 @@ 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); + this._set_value(new Value(j)._value, Value.getBNRoundDown()); return this; }; @@ -771,7 +781,8 @@ Amount.prototype.to_text = function() { } if (this._is_native) { - return this._value.times(Amount.bi_xns_unit).toString(); + return new XRPValue(this._value).multiply( + new Value(Amount.bi_xns_unit))._value.toString(); } // not native @@ -813,7 +824,8 @@ Amount.prototype.applyInterest = function(referenceDate) { return this; } const interest = this._currency.get_interest_at(referenceDate); - return this._copy(this._value.times(interest.toString())); + return this._copy( + new IOUValue(this._value).multiply(new Value(interest.toString()))); }; /** @@ -992,7 +1004,7 @@ Amount.prototype.not_equals_why = function(d, ignore_issuer) { return 'Native mismatch.'; } - let type = this._is_native ? 'XRP' : 'Non-XRP'; + const type = this._is_native ? 'XRP' : 'Non-XRP'; if (!this._value.isZero() && this._value.negated().equals(d._value)) { return type + ' sign differs.'; } diff --git a/src/core/orderbook.js b/src/core/orderbook.js index 0a1ef77f..59725ec1 100644 --- a/src/core/orderbook.js +++ b/src/core/orderbook.js @@ -22,7 +22,7 @@ const Currency = require('./currency').Currency; const AutobridgeCalculator = require('./autobridgecalculator'); const OrderBookUtils = require('./orderbookutils'); const log = require('./log').internal.sub('orderbook'); -const Value_IOU = require('./value_IOU').Value_IOU; +const IOUValue = require('./IOUValue').IOUValue; function assertValidNumber(number, message) { assert(!_.isNull(number) && !isNaN(number), message); @@ -449,9 +449,9 @@ OrderBook.prototype.applyTransferRate = function(balance) { assert(!isNaN(balance), 'Balance is invalid'); assertValidNumber(this._issuerTransferRate, 'Transfer rate is invalid'); - const adjustedBalance = (new Value_IOU(balance)) - .divide(new Value_IOU(this._issuerTransferRate)) - .multiply(new Value_IOU(OrderBook.DEFAULT_TRANSFER_RATE)) + const adjustedBalance = (new IOUValue(balance)) + .divide(new IOUValue(this._issuerTransferRate)) + .multiply(new IOUValue(OrderBook.DEFAULT_TRANSFER_RATE)) ._value.toString(); return adjustedBalance; diff --git a/src/core/orderbookutils.js b/src/core/orderbookutils.js index 2c9136e0..9eaf668a 100644 --- a/src/core/orderbookutils.js +++ b/src/core/orderbookutils.js @@ -12,23 +12,48 @@ function assertValidNumber(number, message) { } /** -* Creates a JSON amount object using +* Creates a new Amount from a JSON amount object using * passed parameters for value, currency and counterparty -* if currency or counterparty is undefined, defaults to IOU * * @param amount of value, currency, counterparty * @return JSON amount object */ function createAmount(value, currency, counterparty) { - if (currency === undefined || counterparty === undefined) { - return {'value': value, - 'currency': '000', - 'issuer': 'rrrrrrrrrrrrrrrrrrrrrhoLvTp'}; - } - return {'value': value, 'currency': currency, 'issuer': 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) { + let currency = offer.TakerPays.currency; + + if (!currency) { + currency = offer.TakerGets.currency; + } + return currency; +} + +/** +* Gets issuer for getOfferTaker(Gets/Pays)Funded +* @param offer +* @return issuer +*/ + +function getIssuerFromOffer(offer) { + let issuer = offer.TakerPays.issuer; + + if (!issuer) { + issuer = offer.TakerGets.issuer; + } + return issuer; +} /** * Casts and returns offer's taker gets funded amount as a default IOU amount @@ -40,16 +65,10 @@ function createAmount(value, currency, counterparty) { OrderBookUtils.getOfferTakerGetsFunded = function(offer) { assertValidNumber(offer.taker_gets_funded, 'Taker gets funded is invalid'); - let currency = offer.TakerPays.currency; - let issuer = offer.TakerPays.issuer; + const currency = getCurrencyFromOffer(offer); + const issuer = getIssuerFromOffer(offer); - if (currency === undefined || issuer === undefined) { - currency = offer.TakerGets.currency; - issuer = offer.TakerGets.issuer; - } - - return Amount.from_json( - createAmount(offer.taker_gets_funded, currency, issuer)); + return createAmount(offer.taker_gets_funded, currency, issuer); }; /** @@ -62,16 +81,10 @@ OrderBookUtils.getOfferTakerGetsFunded = function(offer) { OrderBookUtils.getOfferTakerPaysFunded = function(offer) { assertValidNumber(offer.taker_pays_funded, 'Taker gets funded is invalid'); - let currency = offer.TakerGets.currency; - let issuer = offer.TakerGets.issuer; + const currency = getCurrencyFromOffer(offer); + const issuer = getIssuerFromOffer(offer); - if (currency === undefined || issuer === undefined) { - currency = offer.TakerPays.currency; - issuer = offer.TakerPays.issuer; - } - - return Amount.from_json( - createAmount(offer.taker_pays_funded, currency, issuer)); + return createAmount(offer.taker_pays_funded, currency, issuer); }; /** @@ -85,10 +98,10 @@ OrderBookUtils.getOfferTakerPaysFunded = function(offer) { OrderBookUtils.getOfferTakerGets = function(offer) { assert(typeof offer, 'object', 'Offer is invalid'); - let currency = offer.TakerPays.currency; - let issuer = offer.TakerPays.issuer; + const currency = offer.TakerPays.currency; + const issuer = offer.TakerPays.issuer; - return Amount.from_json(createAmount(offer.TakerGets, currency, issuer)); + return createAmount(offer.TakerGets, currency, issuer); }; /** @@ -110,15 +123,10 @@ OrderBookUtils.getOfferQuality = function(offer, currencyGets) { }); } else { - let currency = offer.TakerGets.currency; - let issuer = offer.TakerGets.issuer; + const currency = getCurrencyFromOffer(offer); + const issuer = getIssuerFromOffer(offer); - if (currency === undefined || issuer === undefined) { - currency = offer.TakerPays.currency; - issuer = offer.TakerPays.issuer; - } - - amount = Amount.from_json(createAmount(offer.quality, currency, issuer)); + amount = createAmount(offer.quality, currency, issuer); } return amount; @@ -136,7 +144,7 @@ OrderBookUtils.getOfferQuality = function(offer, currencyGets) { OrderBookUtils.convertOfferQualityToHex = function(quality) { assert(quality instanceof Amount, 'Quality is not an amount'); - let so = new SerializedObject(); + const so = new SerializedObject(); Types.Quality.serialize(so, quality.to_text()); return so.to_hex(); @@ -148,7 +156,7 @@ OrderBookUtils.convertOfferQualityToHex = function(quality) { OrderBookUtils.normalizeAmount = function(value) { - return Amount.from_json(createAmount(value)); + return Amount.from_number(value); }; module.exports = OrderBookUtils; diff --git a/src/core/serializedtypes.js b/src/core/serializedtypes.js index 022f39a7..43edbdc7 100644 --- a/src/core/serializedtypes.js +++ b/src/core/serializedtypes.js @@ -45,7 +45,7 @@ function isHexInt64String(val) { } function serializeBits(so, bits, noLength) { - let 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) { - let 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) { - let a_field_coordinates = binformat.fieldsInverseMap[a]; - let a_type_bits = a_field_coordinates[0]; - let a_field_bits = a_field_coordinates[1]; - let b_field_coordinates = binformat.fieldsInverseMap[b]; - let b_type_bits = b_field_coordinates[0]; - let 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 @@ -110,7 +110,8 @@ SerializedType.serialize_varint = function(so, val) { }; SerializedType.prototype.parse_varint = function(so) { - let b1 = so.read(1)[0], b2, b3; + const b1 = so.read(1)[0]; + let b2, b3; let result; if (b1 > 254) { @@ -153,7 +154,7 @@ function convertIntegerToByteArray(val, bytes) { throw new Error('Value out of bounds '); } - let newBytes = [ ]; + const newBytes = [ ]; for (let i = 0; i < bytes; i++) { newBytes.unshift(val >>> (i * 8) & 0xff); @@ -172,7 +173,7 @@ function readAndSum(so, bytes) { } for (let i = 0; i < bytes; i++) { - let byte = so.read(1)[0]; + const byte = so.read(1)[0]; sum += (byte << (8 * (bytes - i - 1))); } @@ -195,10 +196,10 @@ 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. - let field_coordinates = binformat.fieldsInverseMap[field_name]; - let type_bits = field_coordinates[0]; - let field_bits = field_coordinates[1]; - let 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 @@ -248,14 +249,14 @@ exports.serialize = exports.serialize_whatever = serialize; // parsing of that. function parse(so) { - let tag_byte = so.read(1)[0]; + const tag_byte = so.read(1)[0]; let type_bits = tag_byte >> 4; if (type_bits === 0) { type_bits = so.read(1)[0]; } - let field_bits = tag_byte & 0x0f; + 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]; @@ -264,7 +265,7 @@ function parse(so) { + tag_byte.toString(16)); // Get the parser class (ST...) for a field based on the type bits. - let type = (field_name === 'Memo') + const type = (field_name === 'Memo') ? exports.STMemo : exports[binformat.types[type_bits]]; @@ -324,7 +325,7 @@ const STInt64 = exports.Int64 = new SerializedType({ serializeBits(so, bigNumObject.toBits(64), true); // noLength = true }, parse: function(so) { - let bytes = so.read(8); + const bytes = so.read(8); return SJCL_BN.fromBits(sjcl.codec.bytes.toBits(bytes)); } }); @@ -333,7 +334,7 @@ STInt64.id = 3; const STHash128 = exports.Hash128 = new SerializedType({ serialize: function(so, val) { - let hash = UInt128.from_json(val); + const hash = UInt128.from_json(val); if (!hash.is_valid()) { throw new Error('Invalid Hash128'); } @@ -348,7 +349,7 @@ STHash128.id = 4; const STHash256 = exports.Hash256 = new SerializedType({ serialize: function(so, val) { - let hash = UInt256.from_json(val); + const hash = UInt256.from_json(val); if (!hash.is_valid()) { throw new Error('Invalid Hash256'); } @@ -363,7 +364,7 @@ STHash256.id = 5; const STHash160 = exports.Hash160 = new SerializedType({ serialize: function(so, val) { - let hash = UInt160.from_json(val); + const hash = UInt160.from_json(val); if (!hash.is_valid()) { throw new Error('Invalid Hash160'); } @@ -379,7 +380,7 @@ STHash160.id = 17; // Internal const STCurrency = new SerializedType({ serialize: function(so, val) { - let currencyData = val.to_bytes(); + const currencyData = val.to_bytes(); if (!currencyData) { throw new Error( @@ -389,8 +390,8 @@ const STCurrency = new SerializedType({ so.append(currencyData); }, parse: function(so) { - let bytes = so.read(20); - let 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 @@ -414,7 +415,7 @@ exports.Quality = new SerializedType({ let value; // if in format: amount/currency/issuer if (val.includes('/')) { - let amount = Amount.from_json(val); + const amount = Amount.from_json(val); if (!amount.is_valid()) { throw new Error('Not a valid Amount object.'); @@ -426,14 +427,14 @@ exports.Quality = new SerializedType({ let hi = 0, lo = 0; - let offset = value.e - 15; + const offset = value.e - 15; if (val !== 0) { // First eight bits: offset/exponent hi |= ((100 + offset) & 0xff) << 24; // Remaining 56 bits: mantissa - let mantissaDecimal = utils.getMantissaDecimalString(value.abs()); - let 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'); @@ -453,14 +454,14 @@ exports.Quality = new SerializedType({ */ const STAmount = exports.Amount = new SerializedType({ serialize: function(so, val) { - let amount = Amount.from_json(val); + const amount = Amount.from_json(val); if (!amount.is_valid()) { throw new Error('Not a valid Amount object.'); } - let value = new BigNumber(amount.to_text()); - let offset = value.e - 15; + const value = new BigNumber(amount.to_text()); + const offset = value.e - 15; // Amount (64-bit integer) let valueBytes = utils.arraySet(8, 0); @@ -506,8 +507,8 @@ const STAmount = exports.Amount = new SerializedType({ hi |= ((97 + offset) & 0xff) << 22; // Remaining 54 bits: mantissa - let mantissaDecimal = utils.getMantissaDecimalString(value.abs()); - let 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'); @@ -522,7 +523,7 @@ const STAmount = exports.Amount = new SerializedType({ if (!amount.is_native()) { // Currency (160-bit hash) - let currency = amount.currency(); + const currency = amount.currency(); STCurrency.serialize(so, currency, true); // Issuer (160-bit hash) @@ -530,27 +531,28 @@ const STAmount = exports.Amount = new SerializedType({ } }, parse: function(so) { - let value_bytes = so.read(8); + const value_bytes = so.read(8); let is_zero = !(value_bytes[0] & 0x7f); for (let i = 1; i < 8; i++) { is_zero = is_zero && !value_bytes[i]; } - let is_negative = !is_zero && !(value_bytes[0] & 0x40); + const is_negative = !is_zero && !(value_bytes[0] & 0x40); if (value_bytes[0] & 0x80) { // non-native - let currency = STCurrency.parse(so); - let issuer_bytes = so.read(20); - let 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); - let offset = ((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6) - 97; - let 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; - let mantissa = new BigNumber(utils.arrayToHex(mantissa_bytes), 16); - let sign = is_negative ? '-' : ''; - let 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, @@ -560,10 +562,10 @@ const STAmount = exports.Amount = new SerializedType({ } // native - let integer_bytes = value_bytes.slice(); + const integer_bytes = value_bytes.slice(); integer_bytes[0] &= 0x3f; - let integer_hex = utils.arrayToHex(integer_bytes); - let 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()); } }); @@ -579,7 +581,7 @@ const STVL = exports.VariableLength = exports.VL = new SerializedType({ } }, parse: function(so) { - let len = this.parse_varint(so); + const len = this.parse_varint(so); return convertByteArrayToHex(so.read(len)); } }); @@ -588,20 +590,20 @@ STVL.id = 7; const STAccount = exports.Account = new SerializedType({ serialize: function(so, val) { - let 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) { - let len = this.parse_varint(so); + const len = this.parse_varint(so); if (len !== 20) { throw new Error('Non-standard-length account ID'); } - let 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()) { @@ -628,7 +630,7 @@ const STPathSet = exports.PathSet = new SerializedType({ } for (let j = 0, l2 = val[i].length; j < l2; j++) { - let entry = val[i][j]; + const entry = val[i][j]; // if (entry.hasOwnProperty('_value')) {entry = entry._value;} let type = 0; @@ -649,7 +651,7 @@ const STPathSet = exports.PathSet = new SerializedType({ } if (entry.currency) { - let currency = Currency.from_json(entry.currency, entry.non_native); + const currency = Currency.from_json(entry.currency, entry.non_native); STCurrency.serialize(so, currency); } @@ -675,7 +677,7 @@ const STPathSet = exports.PathSet = new SerializedType({ amount, currency, issuer. */ - let path_list = []; + const path_list = []; let current_path = []; let tag_byte; @@ -695,7 +697,7 @@ const STPathSet = exports.PathSet = new SerializedType({ } // It's an entry-begin tag. - let entry = {}; + const entry = {}; let type = 0; if (tag_byte & this.typeAccount) { @@ -747,8 +749,8 @@ const STVector256 = exports.Vector256 = new SerializedType({ } }, parse: function(so) { - let length = this.parse_varint(so); - let output = []; + const length = this.parse_varint(so); + const output = []; // length is number of bytes not number of Hash256 for (let i = 0; i < length / 32; i++) { output.push(STHash256.parse(so)); @@ -791,16 +793,16 @@ exports.STMemo = new SerializedType({ } }, parse: function(so) { - let output = {}; + const output = {}; while (so.peek(1)[0] !== 0xe1) { - let keyval = parse(so); + const keyval = parse(so); output[keyval[0]] = keyval[1]; } if (output.MemoType !== undefined) { try { - let parsedType = convertHexToString(output.MemoType); + const parsedType = convertHexToString(output.MemoType); if (parsedType !== 'unformatted_memo') { output.parsed_memo_type = parsedType; @@ -889,9 +891,9 @@ const STObject = exports.Object = new SerializedType({ }, parse: function(so) { - let output = {}; + const output = {}; while (so.peek(1)[0] !== 0xe1) { - let keyval = parse(so); + const keyval = parse(so); output[keyval[0]] = keyval[1]; } so.read(1); @@ -904,15 +906,15 @@ STObject.id = 14; const STArray = exports.Array = new SerializedType({ serialize: function(so, val) { for (let i = 0, l = val.length; i < l; i++) { - let keys = Object.keys(val[i]); + const keys = Object.keys(val[i]); if (keys.length !== 1) { throw new Error( 'Cannot serialize an array containing non-single-key objects'); } - let field_name = keys[0]; - let value = val[i][field_name]; + const field_name = keys[0]; + const value = val[i][field_name]; serialize(so, field_name, value); } @@ -921,11 +923,11 @@ const STArray = exports.Array = new SerializedType({ }, parse: function(so) { - let output = [ ]; + const output = [ ]; while (so.peek(1)[0] !== 0xf1) { - let keyval = parse(so); - let obj = { }; + const keyval = parse(so); + const obj = { }; obj[keyval[0]] = keyval[1]; output.push(obj); } diff --git a/src/core/value.js b/src/core/value.js index ee179c4c..7e7e2869 100644 --- a/src/core/value.js +++ b/src/core/value.js @@ -1,3 +1,5 @@ +/* @flow */ + 'use strict'; const GlobalBigNumber = require('bignumber.js'); @@ -7,49 +9,79 @@ const BigNumber = GlobalBigNumber.another({ DECIMAL_PLACES: 40 }); -function Value(value) { - this._value = new BigNumber(value); +class Value { + + constructor(value: string | BigNumber, base: number) { + this._value = new BigNumber(value, base); + } + + static getBNRoundDown() { + return BigNumber.ROUND_DOWN; + } + + abs() { + const result = this._value.abs(); + return this._canonicalize(result); + } + + add(addend: Value) { + const result = this._value.plus(addend._value); + return this._canonicalize(result); + } + + subtract(subtrahend: Value) { + const result = this._value.minus(subtrahend._value); + return this._canonicalize(result); + } + + multiply(multiplicand: Value) { + const val = this._value; + const mult = multiplicand._value; + const result = (val).times(mult); + return this._canonicalize(result); + } + + scale(scaleFactor: Value) { + const result = this._value.times(scaleFactor._value); + return this._canonicalize(result); + + } + + divide(divisor: Value) { + if (this._value.isNaN()) { + throw new Error('Invalid dividend'); + } + if (divisor.isNaN()) { + throw new Error('Invalid divisor'); + } + 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); + } + + isNaN() { + return this._value.isNaN(); + } + + isZero() { + return this._value.isZero(); + } + + isNegative() { + return this._value.isNegative(); + } + + negated() { + return this._value.neg(); + } + } -Value.prototype.abs = function() { - let result = this._value.abs(); - return this._canonicalize(result); -}; - -Value.prototype.add = function(addend) { - let result = this._value.plus(addend._value); - return this._canonicalize(result); -}; - -Value.prototype.subtract = function(subtrahend) { - let result = this._value.minus(subtrahend._value); - return this._canonicalize(result); -}; - -Value.prototype.multiply = function(multiplicand) { - let val = this._value; - let mult = multiplicand._value; - let result = (val).times(mult); - return this._canonicalize(result); -}; - -Value.prototype.scale = function(scaleFactor) { - let result = this._value.times(scaleFactor._value); - return this._canonicalize(result); - -}; - -Value.prototype.divide = function(divisor) { - if (divisor === 0) { - throw new Error('Divide by zero'); - } - let result = this._value.dividedBy(divisor._value); - return this._canonicalize(result); -}; - -Value.prototype.invert = function() { - let result = (new BigNumber(this._value)).toPower(-1); - return this._canonicalize(result); -}; - exports.Value = Value; diff --git a/src/core/value_IOU.js b/src/core/value_IOU.js deleted file mode 100644 index 2d32b016..00000000 --- a/src/core/value_IOU.js +++ /dev/null @@ -1,54 +0,0 @@ -'use strict'; - -const Value = require('./value').Value; -const GlobalBigNumber = require('bignumber.js'); -const BigNumber = GlobalBigNumber.another({ - ROUNDING_MODE: GlobalBigNumber.ROUND_HALF_UP, - DECIMAL_PLACES: 40 -}); - -function Value_IOU(value) { - Value.call(this, value); -} - -Value_IOU.prototype = Object.create(Value.prototype); - -Value_IOU.prototype.constructor = Value_IOU; - -Value_IOU.fromXRPValue = function(XRPvalue, bi_xns_unit) { - const newV = new Value_IOU(XRPvalue); - newV._XRP = true; - newV._bi_xns_unit = bi_xns_unit; - return newV; -}; - -Value_IOU.prototype.multiply = function(multiplicand) { - let mult = multiplicand; - if (mult._XRP) { - let constant = new BigNumber((mult._bi_xns_unit).toString()); - let value = new BigNumber(mult._value); - mult._value = (value).times(constant); - } - return Value.prototype.multiply.call(this, mult); -}; - -Value_IOU.prototype.divide = function(divisor) { - let div = divisor; - if (div._XRP) { - let constant = new BigNumber((div._bi_xns_unit).toString()); - let value = new BigNumber(div._value); - div._value = (value).times(constant); - } - return Value.prototype.divide.call(this, div); -}; - -Value_IOU.prototype._canonicalize = function(value) { - return new Value_IOU(value.toPrecision(16)); -}; - -Value_IOU.prototype.equals = function(comparator) { - return (comparator instanceof Value_IOU) - && this._value.equals(comparator.value); -}; - -exports.Value_IOU = Value_IOU; diff --git a/src/core/value_XRP.js b/src/core/value_XRP.js deleted file mode 100644 index 3da35a6b..00000000 --- a/src/core/value_XRP.js +++ /dev/null @@ -1,30 +0,0 @@ -'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; - -function Value_XRP(value, bi_xns_unit) { - Value.call(this, value); - this._bi_xns_unit = bi_xns_unit; - this._XRP = true; -} - -Value_XRP.prototype = Object.create(Value.prototype); - -Value_XRP.prototype.constructor = Value_XRP; - -Value_XRP.prototype._canonicalize = function(value) { - return new Value_XRP(value.round(6, BigNumber.ROUND_DOWN)); -}; - -Value_XRP.prototype.equals = function(comparator) { - return (comparator instanceof Value_XRP) - && this._value.equals(comparator.value); -}; - -exports.Value_XRP = Value_XRP; From 6bffe06c3bb556cf4b724ead53ed34f90aaa855d Mon Sep 17 00:00:00 2001 From: Madeline Shortt Date: Thu, 2 Jul 2015 17:53:33 -0700 Subject: [PATCH 4/5] Change this._value to be an instance of IOU or XRP Value --- src/core/IOUValue.js | 36 ++++--- src/core/XRPValue.js | 38 +++++++- src/core/amount.js | 186 ++++++++++++++----------------------- src/core/orderbook.js | 3 +- src/core/orderbookutils.js | 14 +-- src/core/utils.js | 12 ++- src/core/value.js | 60 ++++++++---- test/amount-test.js | 8 +- 8 files changed, 186 insertions(+), 171 deletions(-) diff --git a/src/core/IOUValue.js b/src/core/IOUValue.js index 3ae3e242..f8c61ead 100644 --- a/src/core/IOUValue.js +++ b/src/core/IOUValue.js @@ -3,6 +3,7 @@ '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, @@ -11,33 +12,44 @@ const BigNumber = GlobalBigNumber.another({ class IOUValue extends Value { - constructor(value: string | BigNumber) { - super(value); + constructor(value: string | BigNumber, roundingMode: ?number = null, + base: ?number = null) { + + super(new BigNumber(value, base).toDigits(16, roundingMode)); } - multiplyByXRP(multiplicand: {_value: BigNumber, _bi_xns_unit: number}) { - const constant = new BigNumber((multiplicand._bi_xns_unit).toString()); - const value = new BigNumber(multiplicand._value); - multiplicand._value = (value).times(constant); + multiply(multiplicand: Value) { + if (multiplicand instanceof XRPValue) { + return super.multiply( + new IOUValue( + multiplicand._value.times(multiplicand.rippleUnits))); + } return super.multiply(multiplicand); } - divideByXRP(divisor: {_value: BigNumber, _bi_xns_unit: number}) { - const constant = new BigNumber((divisor._bi_xns_unit).toString()); - const value = new BigNumber(divisor._value); - divisor._value = (value).times(constant); + divide(divisor: Value) { + if (divisor instanceof XRPValue) { + return super.divide( + new IOUValue(divisor._value.times(divisor.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); + && this._value.equals(comparator._value); } - } exports.IOUValue = IOUValue; diff --git a/src/core/XRPValue.js b/src/core/XRPValue.js index f68ec198..286fce39 100644 --- a/src/core/XRPValue.js +++ b/src/core/XRPValue.js @@ -1,3 +1,5 @@ +/* @flow */ + 'use strict'; const GlobalBigNumber = require('bignumber.js'); @@ -10,20 +12,48 @@ const Value = require('./value').Value; class XRPValue extends Value { - constructor(value, bi_xns_unit) { + constructor(value: string | BigNumber) { super(value); - this._bi_xns_unit = bi_xns_unit; + this.rippleUnits = new BigNumber(1e6); + 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(multiplicand.rippleUnits))); + } + return super.multiply(multiplicand); + } + + divide(divisor: Value) { + if (divisor instanceof XRPValue) { + return super.divide( + new XRPValue(divisor._value.times(divisor.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); + && this._value.equals(comparator._value); } - } exports.XRPValue = XRPValue; diff --git a/src/core/amount.js b/src/core/amount.js index 5797feb2..6caec5ce 100644 --- a/src/core/amount.js +++ b/src/core/amount.js @@ -13,17 +13,13 @@ const Value = require('./value').Value; const IOUValue = require('./IOUValue').IOUValue; const XRPValue = require('./XRPValue').XRPValue; - -function inverse(number) { - return new IOUValue(number).invert()._value; -} - -function Amount() { +function Amount(value = new XRPValue(NaN)) { // Json format: // integer : XRP // { 'value' : ..., 'currency' : ..., 'issuer' : ...} + assert(value instanceof Value); - this._value = new Value(NaN)._value; + 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(); @@ -41,19 +37,11 @@ const consts = { xns_precision: 6, // bi_ prefix refers to "big integer" - // TODO: we shouldn't expose our BigNumber library publicly - bi_5: new Value(5)._value, - bi_7: new Value(7)._value, - bi_10: new Value(10)._value, - bi_1e14: new Value(1e14)._value, - bi_1e16: new Value(1e16)._value, - bi_1e17: new Value(1e17)._value, - bi_1e32: new Value(1e32)._value, - bi_man_max_value: new Value('9999999999999999')._value, - bi_man_min_value: new Value(1e15)._value, - bi_xns_max: new Value(1e17)._value, - bi_xns_min: new Value(-1e17)._value, - bi_xns_unit: new Value(1e6)._value, + // 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, @@ -65,9 +53,11 @@ const consts = { min_value: '-1000000000000000e-96' }; -const MAX_XRP_VALUE = new Value(1e11)._value; -const MAX_IOU_VALUE = new Value(consts.max_value)._value; -const MIN_IOU_VALUE = new Value(consts.min_value)._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); @@ -111,21 +101,15 @@ Amount.is_valid_full = function(j) { Amount.NaN = function() { const result = new Amount(); - result._value = new Value(NaN)._value; // should have no effect + 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 Object); +Amount.prototype._set_value = function(value: Value) { - this._value = this._value = value.isZero() && value.isNegative() ? - value.negated()._value : value._value; - if (!this._value) { - this._value = value.isZero() && value.isNegative() ? - value.negated() : value; - } - this.canonicalize(roundingMode); + this._value = value.isZero() && value.isNegative() ? + value.negate() : value; this._check_limits(); }; @@ -133,8 +117,7 @@ Amount.prototype._set_value = function(value, roundingMode) { // Returns a new value which is the absolute value of this. Amount.prototype.abs = function() { - const val = (new IOUValue(this._value)).abs(); - return this._copy(val); + return this._copy(this._value.abs()); }; @@ -142,12 +125,10 @@ Amount.prototype.add = function(addend) { const addendAmount = Amount.from_json(addend); if (!this.is_comparable(addendAmount)) { - return new Amount(NaN); + return new Amount(); } - const thisValue = new IOUValue(this._value); - const addendValue = new IOUValue(addendAmount._value); - return this._copy(thisValue.add(addendValue)); + return this._copy(this._value.add(addendAmount._value)); }; @@ -160,17 +141,8 @@ Amount.prototype.subtract = function(subtrahend) { Amount.prototype.multiply = function(multiplicand) { const multiplicandAmount = Amount.from_json(multiplicand); - const thisValue = new IOUValue(this._value); - let multiplyBy; - - if (multiplicandAmount.is_native()) { - multiplyBy = new XRPValue(multiplicandAmount._value, Amount.bi_xns_unit); - return this._copy(thisValue.multiplyByXRP(multiplyBy)._value); - } - - multiplyBy = new IOUValue(multiplicandAmount._value); - return this._copy(thisValue.multiply(multiplyBy)); + return this._copy(this._value.multiply(multiplicandAmount._value)); }; @@ -180,17 +152,8 @@ Amount.prototype.scale = function(scaleFactor) { Amount.prototype.divide = function(divisor) { const divisorAmount = Amount.from_json(divisor); - const thisValue = new IOUValue(this._value); - let divideBy; - - if (divisorAmount.is_native()) { - divideBy = new XRPValue(divisorAmount._value, Amount.bi_xns_unit); - return this._copy(thisValue.divideByXRP(divideBy)._value); - } - - divideBy = new IOUValue(divisorAmount._value); - return this._copy(thisValue.divide(divideBy)); + return this._copy(this._value.divide(divisorAmount._value)); }; /** @@ -248,7 +211,7 @@ Amount.prototype.ratio_human = function(denom, opts) { // // To compensate, we multiply the numerator by 10^xns_precision. if (denominator._is_native) { - numerator._set_value(numerator.multiply(Amount.bi_xns_unit)); + numerator._set_value(numerator.multiply(bi_xns_unit)); } return numerator.divide(denominator); @@ -268,20 +231,20 @@ Amount.prototype.ratio_human = function(denom, opts) { * * @see Amount#ratio_human * - * @param {Amount} fac The second factor of the product. + * @param {Amount} factor The second factor of the product. * @param {Object} opts Options for the calculation. * @param {Date|Number} opts.reference_date Date based on which * demurrage/interest should be applied. Can be given as JavaScript Date or int * for Ripple epoch. * @return {Amount} The product. Unit will be the same as the first factor. */ -Amount.prototype.product_human = function(fac, options = {}) { +Amount.prototype.product_human = function(factor, options = {}) { - let factor = Amount.from_json(fac); + 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 @@ -289,17 +252,17 @@ Amount.prototype.product_human = function(fac, options = {}) { // 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 (options.reference_date) { - factor = factor.applyInterest(options.reference_date); + fac = fac.applyInterest(options.reference_date); } - const 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) { - const quotient = product.divide(Amount.bi_xns_unit.toString()); + if (fac._is_native) { + const quotient = product.divide(bi_xns_unit.toString()); product._set_value(quotient._value); } @@ -313,7 +276,7 @@ Amount.prototype.product_human = function(fac, options = {}) { * @private */ Amount.prototype._invert = function() { - this._set_value(inverse(this._value)); + this._set_value(this._value.invert()); return this; }; @@ -328,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 @@ -357,19 +320,6 @@ Amount.prototype.invert = function() { * bigger than supported */ -Amount.prototype.canonicalize = function(roundingMode) { - if (typeof this._value === 'undefined') { - throw new Error('undefined value'); - } - if (this._is_native) { - this._value = this._value.round(6, Value.getBNRoundDown()); - } else if (roundingMode) { - this._value = new Value(this._value.toPrecision(16, roundingMode))._value; - } else { - this._value = new Value(this._value.toPrecision(16))._value; - } -}; - Amount.prototype._check_limits = function() { if (!Amount.strict_mode) { return this; @@ -377,7 +327,7 @@ Amount.prototype._check_limits = function() { if (this._value.isNaN() || this._value.isZero()) { return this; } - const 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()); @@ -406,7 +356,7 @@ Amount.prototype._copy = function(value) { Amount.prototype.compareTo = function(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); }; @@ -414,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; @@ -525,7 +475,7 @@ Amount.prototype.parse_human = function(j, options) { 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) { @@ -539,22 +489,25 @@ Amount.prototype.parse_human = function(j, options) { 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 Value(value)._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()) { const interest = this._currency.get_interest_at(opts.reference_date); this._set_value( - new IOUValue(this._value).divide(new Value(interest.toString()))); + this._value.divide(new IOUValue(interest.toString()))); } return this; @@ -608,11 +561,9 @@ function(quality, counterCurrency, counterIssuer, opts) { const mantissa_hex = quality.substring(quality.length - 14); const offset_hex = quality.substring( quality.length - 16, quality.length - 14); - const mantissa = new Value(mantissa_hex, 16)._value; + const mantissa = new IOUValue(mantissa_hex, null, 16); const offset = parseInt(offset_hex, 16) - 100; - const valueStr = mantissa.toString() + 'e' + offset.toString(); - const value = new Value(valueStr)._value; this._currency = Currency.from_json(counterCurrency); this._issuer = UInt160.from_json(counterIssuer); this._is_native = this._currency.is_native(); @@ -634,9 +585,9 @@ function(quality, counterCurrency, counterIssuer, opts) { quality as stored : 5 USD / 3000000 drops inverted : 3000000 drops / 5 USD */ - const adjusted = options.inverse ? inverse(value) : value; - let nativeAdjusted = this._is_native ? - new XRPValue(adjusted, Amount.bi_xns_unit) : new IOUValue(adjusted); + const valueStr = mantissa.toString() + 'e' + offset.toString(); + let nativeAdjusted = new IOUValue(valueStr); + nativeAdjusted = options.inverse ? nativeAdjusted.invert() : nativeAdjusted; if (!options.xrp_as_drops) { // `In a currency exchange, the exchange rate is quoted as the units of the @@ -647,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 = nativeAdjusted.divide(new Value(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 = nativeAdjusted.multiply(new Value(Amount.bi_xns_unit)); + nativeAdjusted = nativeAdjusted.multiply(bi_xns_unit); } } - this._set_value(nativeAdjusted); + if (this._is_native) { + this._set_value( + new XRPValue(nativeAdjusted.round(6, Value.getBNRoundDown()))); + } 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( - new IOUValue(this._value).divide(new Value(interest.toString()))); + this._value.divide(new IOUValue(interest.toString()))); } - return this; }; @@ -670,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 Value(n)._value); + this._set_value(new IOUValue(n)); return this; }; @@ -721,7 +676,7 @@ Amount.prototype.parse_json = function(j) { break; default: - this._set_value(new Value(NaN)._value); + this._set_value(new IOUValue(NaN)); } return this; @@ -736,11 +691,11 @@ Amount.prototype.parse_native = function(j) { if (j.indexOf('.') >= 0) { throw new Error('Native amounts must be specified in integer drops'); } - const value = new XRPValue(j, Amount.bi_xns_unit); + const value = new XRPValue(j); this._is_native = true; - this._set_value(value.divide(new Value(Amount.bi_xns_unit))); + this._set_value(value.divide(bi_xns_unit)); } else { - this._set_value(new Value(NaN)._value); + this._set_value(new IOUValue(NaN)); } return this; @@ -750,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 Value(j)._value, Value.getBNRoundDown()); + const newValue = new IOUValue(j, Value.getBNRoundDown()); + this._set_value(newValue); return this; }; @@ -781,14 +737,14 @@ Amount.prototype.to_text = function() { } if (this._is_native) { - return new XRPValue(this._value).multiply( - new Value(Amount.bi_xns_unit))._value.toString(); + return this._value.multiply(bi_xns_unit).toString(); } // not native - const offset = this._value.e - 15; + const offset = this._value.getExponent() - 15; const sign = this._value.isNegative() ? '-' : ''; - const mantissa = utils.getMantissaDecimalString(this._value.absoluteValue()); + const mantissa = utils.getMantissa16FromString( + this._value.abs().toString()); if (offset !== 0 && (offset < -25 || offset > -4)) { // Use e notation. // XXX Clamp output. @@ -825,7 +781,7 @@ Amount.prototype.applyInterest = function(referenceDate) { } const interest = this._currency.get_interest_at(referenceDate); return this._copy( - new IOUValue(this._value).multiply(new Value(interest.toString()))); + this._value.multiply(new IOUValue(interest.toString()))); }; /** @@ -1005,7 +961,7 @@ Amount.prototype.not_equals_why = function(d, ignore_issuer) { } const type = this._is_native ? 'XRP' : 'Non-XRP'; - if (!this._value.isZero() && this._value.negated().equals(d._value)) { + if (!this._value.isZero() && this._value.negate().equals(d._value)) { return type + ' sign differs.'; } if (!this._value.equals(d._value)) { diff --git a/src/core/orderbook.js b/src/core/orderbook.js index 59725ec1..0e173e3f 100644 --- a/src/core/orderbook.js +++ b/src/core/orderbook.js @@ -451,8 +451,7 @@ OrderBook.prototype.applyTransferRate = function(balance) { const adjustedBalance = (new IOUValue(balance)) .divide(new IOUValue(this._issuerTransferRate)) - .multiply(new IOUValue(OrderBook.DEFAULT_TRANSFER_RATE)) - ._value.toString(); + .multiply(new IOUValue(OrderBook.DEFAULT_TRANSFER_RATE)).toString(); return adjustedBalance; }; diff --git a/src/core/orderbookutils.js b/src/core/orderbookutils.js index 9eaf668a..34705dcc 100644 --- a/src/core/orderbookutils.js +++ b/src/core/orderbookutils.js @@ -32,12 +32,7 @@ function createAmount(value, currency, counterparty) { */ function getCurrencyFromOffer(offer) { - let currency = offer.TakerPays.currency; - - if (!currency) { - currency = offer.TakerGets.currency; - } - return currency; + return offer.TakerPays.currency || offer.TakerGets.currency; } /** @@ -47,12 +42,7 @@ function getCurrencyFromOffer(offer) { */ function getIssuerFromOffer(offer) { - let issuer = offer.TakerPays.issuer; - - if (!issuer) { - issuer = offer.TakerGets.issuer; - } - return issuer; + return offer.TakerPays.issuer || offer.TakerGets.issuer; } /** diff --git a/src/core/utils.js b/src/core/utils.js index e8fe8f3c..2d9de2fa 100644 --- a/src/core/utils.js +++ b/src/core/utils.js @@ -1,8 +1,9 @@ '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 while (mantissa.length < 16) { @@ -11,6 +12,10 @@ function getMantissaDecimalString(bignum) { 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 +161,7 @@ exports.arrayUnique = arrayUnique; exports.toTimestamp = toTimestamp; exports.fromTimestamp = fromTimestamp; exports.getMantissaDecimalString = getMantissaDecimalString; +exports.getMantissa16FromString = getMantissa16FromString; exports.sjcl = require('sjcl-extended'); diff --git a/src/core/value.js b/src/core/value.js index 7e7e2869..eea11495 100644 --- a/src/core/value.js +++ b/src/core/value.js @@ -9,10 +9,16 @@ const BigNumber = GlobalBigNumber.another({ DECIMAL_PLACES: 40 }); +const assert = require('assert'); + class Value { - constructor(value: string | BigNumber, base: number) { - this._value = new BigNumber(value, base); + 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() { @@ -25,35 +31,23 @@ class Value { } 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 val = this._value; - const mult = multiplicand._value; - const result = (val).times(mult); + const result = this._value.times(multiplicand._value); return this._canonicalize(result); } - scale(scaleFactor: Value) { - const result = this._value.times(scaleFactor._value); - return this._canonicalize(result); - - } - divide(divisor: Value) { - if (this._value.isNaN()) { - throw new Error('Invalid dividend'); - } - if (divisor.isNaN()) { - throw new Error('Invalid divisor'); - } if (divisor.isZero()) { throw new Error('divide by zero'); } @@ -66,6 +60,19 @@ class Value { 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(); } @@ -78,8 +85,23 @@ class Value { return this._value.isNegative(); } - negated() { - return this._value.neg(); + 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); } } diff --git a/test/amount-test.js b/test/amount-test.js index 72339c40..3deec477 100644 --- a/test/amount-test.js +++ b/test/amount-test.js @@ -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() { From 2ac4549712bc77d0bce8ed3043df1a32deb157d5 Mon Sep 17 00:00:00 2001 From: Madeline Shortt Date: Wed, 8 Jul 2015 10:23:13 -0700 Subject: [PATCH 5/5] Rename IOUValue.js and XRPValue.js to lowercase and other fixes --- src/core/amount.js | 6 +++--- src/core/{IOUValue.js => iouvalue.js} | 7 ++++--- src/core/orderbook.js | 2 +- src/core/utils.js | 3 +++ src/core/{XRPValue.js => xrpvalue.js} | 6 +++--- 5 files changed, 14 insertions(+), 10 deletions(-) rename src/core/{IOUValue.js => iouvalue.js} (84%) rename src/core/{XRPValue.js => xrpvalue.js} (86%) diff --git a/src/core/amount.js b/src/core/amount.js index 6caec5ce..18a1baa2 100644 --- a/src/core/amount.js +++ b/src/core/amount.js @@ -10,8 +10,8 @@ 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; +const IOUValue = require('./iouvalue').IOUValue; +const XRPValue = require('./xrpvalue').XRPValue; function Amount(value = new XRPValue(NaN)) { // Json format: @@ -607,7 +607,7 @@ function(quality, counterCurrency, counterIssuer, opts) { } if (this._is_native) { this._set_value( - new XRPValue(nativeAdjusted.round(6, Value.getBNRoundDown()))); + new XRPValue(nativeAdjusted.round(6, Value.getBNRoundDown()).toString())); } else { this._set_value(nativeAdjusted); } diff --git a/src/core/IOUValue.js b/src/core/iouvalue.js similarity index 84% rename from src/core/IOUValue.js rename to src/core/iouvalue.js index f8c61ead..babffecd 100644 --- a/src/core/IOUValue.js +++ b/src/core/iouvalue.js @@ -3,12 +3,13 @@ 'use strict'; const Value = require('./value').Value; -const XRPValue = require('./XRPValue').XRPValue; +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 { @@ -22,7 +23,7 @@ class IOUValue extends Value { if (multiplicand instanceof XRPValue) { return super.multiply( new IOUValue( - multiplicand._value.times(multiplicand.rippleUnits))); + multiplicand._value.times(rippleUnits))); } return super.multiply(multiplicand); } @@ -30,7 +31,7 @@ class IOUValue extends Value { divide(divisor: Value) { if (divisor instanceof XRPValue) { return super.divide( - new IOUValue(divisor._value.times(divisor.rippleUnits))); + new IOUValue(divisor._value.times(rippleUnits))); } return super.divide(divisor); } diff --git a/src/core/orderbook.js b/src/core/orderbook.js index 0e173e3f..989f25bd 100644 --- a/src/core/orderbook.js +++ b/src/core/orderbook.js @@ -22,7 +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; +const IOUValue = require('./iouvalue').IOUValue; function assertValidNumber(number, message) { assert(!_.isNull(number) && !isNaN(number), message); diff --git a/src/core/utils.js b/src/core/utils.js index 2d9de2fa..6341a6c3 100644 --- a/src/core/utils.js +++ b/src/core/utils.js @@ -6,6 +6,9 @@ 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 } diff --git a/src/core/XRPValue.js b/src/core/xrpvalue.js similarity index 86% rename from src/core/XRPValue.js rename to src/core/xrpvalue.js index 286fce39..47107847 100644 --- a/src/core/XRPValue.js +++ b/src/core/xrpvalue.js @@ -9,12 +9,12 @@ const BigNumber = GlobalBigNumber.another({ }); const Value = require('./value').Value; +const rippleUnits = new BigNumber(1e6); class XRPValue extends Value { constructor(value: string | BigNumber) { super(value); - this.rippleUnits = new BigNumber(1e6); if (this._value.dp() > 6) { throw new Error( 'Value has more than 6 digits of precision past the decimal point, ' @@ -26,7 +26,7 @@ class XRPValue extends Value { multiply(multiplicand: Value) { if (multiplicand instanceof XRPValue) { return super.multiply( - new XRPValue(multiplicand._value.times(multiplicand.rippleUnits))); + new XRPValue(multiplicand._value.times(rippleUnits))); } return super.multiply(multiplicand); } @@ -34,7 +34,7 @@ class XRPValue extends Value { divide(divisor: Value) { if (divisor instanceof XRPValue) { return super.divide( - new XRPValue(divisor._value.times(divisor.rippleUnits))); + new XRPValue(divisor._value.times(rippleUnits))); } return super.divide(divisor); }