From c7bbce83719c1e8c6a4fae5ca850e7515db1a4a5 Mon Sep 17 00:00:00 2001 From: Bo Chen Date: Mon, 16 Mar 2015 15:42:13 -0700 Subject: [PATCH] [FEATURE] add offer autobridging --- src/js/ripple/autobridgecalculator.js | 435 +++++++++++++++ src/js/ripple/orderbook.js | 317 ++++++----- src/js/ripple/orderbookutils.js | 106 ++++ src/js/ripple/serializedtypes.js | 112 ++-- test/fixtures/orderbook.js | 120 +++- test/orderbook-autobridge-test.js | 753 ++++++++++++++++++++++++++ test/orderbook-test.js | 155 +----- test/serializedtypes-test.js | 650 ++++++++++++---------- 8 files changed, 2045 insertions(+), 603 deletions(-) create mode 100644 src/js/ripple/autobridgecalculator.js create mode 100644 src/js/ripple/orderbookutils.js create mode 100644 test/orderbook-autobridge-test.js diff --git a/src/js/ripple/autobridgecalculator.js b/src/js/ripple/autobridgecalculator.js new file mode 100644 index 00000000..7b0dec80 --- /dev/null +++ b/src/js/ripple/autobridgecalculator.js @@ -0,0 +1,435 @@ +'use strict'; + +var _ = require('lodash'); +var assert = require('assert'); +var UInt160 = require('./uint160').UInt160; +var Amount = require('./amount').Amount; +var Utils = require('./orderbookutils'); + +function assertValidNumber(number, message) { + assert(!_.isNull(number) && !isNaN(number), message); +} + +function assertValidLegOneOffer(legOneOffer, message) { + assert(legOneOffer); + assert.strictEqual(typeof legOneOffer, 'object', message); + assert.strictEqual(typeof legOneOffer.TakerPays, 'object', message); + assertValidNumber(legOneOffer.TakerGets, message); +} + +function AutobridgeCalculator(currencyGets, currencyPays, + legOneOffers, legTwoOffers) { + this._currencyGets = currencyGets; + this._currencyPays = currencyPays; + this.legOneOffers = _.cloneDeep(legOneOffers); + this.legTwoOffers = _.cloneDeep(legTwoOffers); + + this._ownerFundsLeftover = {}; +} + +/** + * Calculates an ordered array of autobridged offers by quality + * + * @return {Array} + */ + +AutobridgeCalculator.prototype.calculate = function() { + var legOnePointer = 0; + var legTwoPointer = 0; + + var offersAutobridged = []; + + this.clearOwnerFundsLeftover(); + + while (this.legOneOffers[legOnePointer] && this.legTwoOffers[legTwoPointer]) { + var legOneOffer = this.legOneOffers[legOnePointer]; + var legTwoOffer = this.legTwoOffers[legTwoPointer]; + var leftoverFunds = this.getLeftoverOwnerFunds(legOneOffer.Account); + var autobridgedOffer; + + if (legOneOffer.Account === legTwoOffer.Account) { + this.unclampLegOneOwnerFunds(legOneOffer); + } else if (!legOneOffer.is_fully_funded && !leftoverFunds.is_zero()) { + this.adjustLegOneFundedAmount(legOneOffer); + } + + var legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer); + var legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer); + + if (legOneTakerGetsFunded.is_zero()) { + legOnePointer++; + + continue; + } + + if (legTwoTakerPaysFunded.is_zero()) { + legTwoPointer++; + + continue; + } + + if (legOneTakerGetsFunded.compareTo(legTwoTakerPaysFunded) > 0) { + autobridgedOffer = this.getAutobridgedOfferWithClampedLegOne( + legOneOffer, + legTwoOffer + ); + + legTwoPointer++; + } else if (legTwoTakerPaysFunded.compareTo(legOneTakerGetsFunded) > 0) { + autobridgedOffer = this.getAutobridgedOfferWithClampedLegTwo( + legOneOffer, + legTwoOffer + ); + + legOnePointer++; + } else { + autobridgedOffer = this.getAutobridgedOfferWithoutClamps( + legOneOffer, + legTwoOffer + ); + + legOnePointer++; + legTwoPointer++; + } + + offersAutobridged.push(autobridgedOffer); + } + + return offersAutobridged; +}; + +/** + * In this case, the output from leg one is greater than the input to leg two. + * Therefore, we must effectively clamp leg one output to leg two input. + * + * @param {Object} legOneOffer + * @param {Object} legTwoOffer + * + * @return {Object} + */ + +AutobridgeCalculator.prototype.getAutobridgedOfferWithClampedLegOne = +function(legOneOffer, legTwoOffer) { + var legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer); + var legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer); + var legOneQuality = Utils.getOfferQuality(legOneOffer, this._currencyGets); + + var autobridgedTakerGets = Utils.getOfferTakerGetsFunded(legTwoOffer); + var autobridgedTakerPays = legTwoTakerPaysFunded.multiply(legOneQuality); + + if (legOneOffer.Account === legTwoOffer.Account) { + var legOneTakerGets = Utils.getOfferTakerGets(legOneOffer); + var updatedTakerGets = legOneTakerGets.subtract(legTwoTakerPaysFunded); + + this.setLegOneTakerGets(legOneOffer, updatedTakerGets); + + this.clampLegOneOwnerFunds(legOneOffer); + } else { + // Update funded amount since leg one offer was not completely consumed + var updatedTakerGetsFunded = legOneTakerGetsFunded + .subtract(legTwoTakerPaysFunded); + + this.setLegOneTakerGetsFunded(legOneOffer, updatedTakerGetsFunded); + } + + return this.formatAutobridgedOffer( + autobridgedTakerGets, + autobridgedTakerPays + ); +}; + +/** + * In this case, the input from leg two is greater than the output to leg one. + * Therefore, we must effectively clamp leg two input to leg one output. + * + * @param {Object} legOneOffer + * @param {Object} legTwoOffer + * + * @return {Object} + */ + +AutobridgeCalculator.prototype.getAutobridgedOfferWithClampedLegTwo = +function(legOneOffer, legTwoOffer) { + var legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer); + var legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer); + var legTwoQuality = Utils.getOfferQuality(legTwoOffer, this._currencyGets); + + var autobridgedTakerGets = legOneTakerGetsFunded.divide(legTwoQuality); + var autobridgedTakerPays = Utils.getOfferTakerPaysFunded(legOneOffer); + + // Update funded amount since leg two offer was not completely consumed + legTwoOffer.taker_gets_funded = Utils.getOfferTakerGetsFunded(legTwoOffer) + .subtract(autobridgedTakerGets) + .to_text(); + legTwoOffer.taker_pays_funded = legTwoTakerPaysFunded + .subtract(legOneTakerGetsFunded) + .to_text(); + + return this.formatAutobridgedOffer( + autobridgedTakerGets, + autobridgedTakerPays + ); +}; + +/** + * In this case, the output from leg one and the input to leg two are the same. + * We do not need to clamp either. + * @param {Object} legOneOffer + * @param {Object} legTwoOffer + * + * @return {Object} + */ + +AutobridgeCalculator.prototype.getAutobridgedOfferWithoutClamps = +function(legOneOffer, legTwoOffer) { + var autobridgedTakerGets = Utils.getOfferTakerGetsFunded(legTwoOffer); + var autobridgedTakerPays = Utils.getOfferTakerPaysFunded(legOneOffer); + + return this.formatAutobridgedOffer( + autobridgedTakerGets, + autobridgedTakerPays + ); +}; + +/** + * Clear owner funds leftovers + */ + +AutobridgeCalculator.prototype.clearOwnerFundsLeftover = function() { + this._ownerFundsLeftover = {}; +}; + +/** + * Reset owner funds leftovers for an account to 0 + * + * @param {String} account + * + * @return {Amount} + */ + +AutobridgeCalculator.prototype.resetOwnerFundsLeftover = function(account) { + assert(UInt160.is_valid(account), 'Account is invalid'); + + this._ownerFundsLeftover[account] = Utils.normalizeAmount('0'); + + return this._ownerFundsLeftover[account]; +}; + +/** + * Retrieve leftover funds found after clamping leg one by account + * + * @param {String} account + * + * @return {Amount} + */ + +AutobridgeCalculator.prototype.getLeftoverOwnerFunds = function(account) { + assert(UInt160.is_valid(account), 'Account is invalid'); + + var amount = this._ownerFundsLeftover[account]; + + if (!amount) { + amount = Utils.normalizeAmount('0'); + } + + return amount; +}; + +/** + * Add funds to account's leftover funds + * + * @param {String} account + * @param {Amount} amount + * + * @return {Amount} + */ + +AutobridgeCalculator.prototype.addLeftoverOwnerFunds = +function(account, amount) { + assert(UInt160.is_valid(account), 'Account is invalid'); + assert(amount instanceof Amount, 'Amount is invalid'); + + this._ownerFundsLeftover[account] = this.getLeftoverOwnerFunds(account) + .add(amount); + + return this._ownerFundsLeftover[account]; +}; + +/** + * Set account's leftover funds + * + * @param {String} account + * @param {Amount} amount + */ + +AutobridgeCalculator.prototype.setLeftoverOwnerFunds = +function(account, amount) { + assert(UInt160.is_valid(account), 'Account is invalid'); + assert(amount instanceof Amount, 'Amount is invalid'); + + this._ownerFundsLeftover[account] = amount; +}; + +/** + * Format an autobridged offer and compute synthetic values (e.g. quality) + * + * @param {Amount} takerGets + * @param {Amount} takerPays + * + * @return {Object} + */ + +AutobridgeCalculator.prototype.formatAutobridgedOffer = +function(takerGets, takerPays) { + assert(takerGets instanceof Amount, 'Autobridged taker gets is invalid'); + assert(takerPays instanceof Amount, 'Autobridged taker pays is invalid'); + + var autobridgedOffer = {}; + var quality = takerPays.divide(takerGets); + + autobridgedOffer.TakerGets = { + value: takerGets.to_text(), + currency: this._currencyGets.to_hex(), + issuer: this._issuerGets + }; + + autobridgedOffer.TakerPays = { + value: takerPays.to_text(), + currency: this._currencyPays.to_hex(), + issuer: this._issuerPays + }; + + autobridgedOffer.quality = quality.to_text(); + + autobridgedOffer.taker_gets_funded = autobridgedOffer.TakerGets.value; + autobridgedOffer.taker_pays_funded = autobridgedOffer.TakerPays.value; + + autobridgedOffer.autobridged = true; + + autobridgedOffer.BookDirectory = Utils.convertOfferQualityToHex(quality); + + return autobridgedOffer; +}; + +/** + * Remove funds clamp on leg one offer. This is necessary when the two offers + * are owned by the same account. In this case, it doesn't matter if offer one + * is not fully funded. Leg one out goes to leg two in and since its the same + * account, an infinite amount can flow. + * + * @param {Object} legOneOffer - IOU:XRP offer + */ + +AutobridgeCalculator.prototype.unclampLegOneOwnerFunds = function(legOneOffer) { + assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid'); + + legOneOffer.initTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer); + + this.setLegOneTakerGetsFunded( + legOneOffer, + Utils.getOfferTakerGets(legOneOffer) + ); +}; + +/** + * Apply clamp back on leg one offer after a round of autobridge calculation + * completes. We must reapply clamps that have been removed because we cannot + * guarantee that the next offer from leg two will also be from the same + * account. + * + * When we reapply, it could happen that the amount of TakerGets left after + * the autobridge calculation is less than the original funded amount. In this + * case, we have extra funds we can use towards unfunded offers with worse + * quality by the same owner. + * + * @param {Object} legOneOffer - IOU:XRP offer + */ + +AutobridgeCalculator.prototype.clampLegOneOwnerFunds = function(legOneOffer) { + assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid'); + + var takerGets = Utils.getOfferTakerGets(legOneOffer); + + if (takerGets.compareTo(legOneOffer.initTakerGetsFunded) > 0) { + // After clamping, TakerGets is still greater than initial funded amount + this.setLegOneTakerGetsFunded(legOneOffer, legOneOffer.initTakerGetsFunded); + } else { + var updatedLeftover = legOneOffer.initTakerGetsFunded.subtract(takerGets); + + this.setLegOneTakerGetsFunded(legOneOffer, takerGets); + this.addLeftoverOwnerFunds(legOneOffer.Account, updatedLeftover); + } +}; + +/** + * Increase leg one offer funded amount with extra funds found after applying + * clamp. + * + * @param {Object} legOneOffer - IOU:XRP offer + */ + +AutobridgeCalculator.prototype.adjustLegOneFundedAmount = +function(legOneOffer) { + assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid'); + assert(!legOneOffer.is_fully_funded, 'Leg one offer cannot be fully funded'); + + var fundedSum = Utils.getOfferTakerGetsFunded(legOneOffer) + .add(this.getLeftoverOwnerFunds(legOneOffer.Account)); + + if (fundedSum.compareTo(Utils.getOfferTakerGets(legOneOffer)) >= 0) { + // There are enough extra funds to fully fund the offer + var legOneTakerGets = Utils.getOfferTakerGets(legOneOffer); + var updatedLeftover = fundedSum.subtract(legOneTakerGets); + + this.setLegOneTakerGetsFunded(legOneOffer, legOneTakerGets); + this.setLeftoverOwnerFunds(legOneOffer.Account, updatedLeftover); + } else { + // There are not enough extra funds to fully fund the offer + this.setLegOneTakerGetsFunded(legOneOffer, fundedSum); + this.resetOwnerFundsLeftover(legOneOffer.Account); + } +}; + +/** + * Set taker gets funded amount for a IOU:XRP offer. Also calculates taker + * pays funded using offer quality and updates is_fully_funded flag + * + * @param {Object} legOneOffer - IOU:XRP offer + * @param {Amount} takerGetsFunded + */ + +AutobridgeCalculator.prototype.setLegOneTakerGetsFunded = +function setLegOneTakerGetsFunded(legOneOffer, takerGetsFunded) { + assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid'); + assert(takerGetsFunded instanceof Amount, 'Taker gets funded is invalid'); + + legOneOffer.taker_gets_funded = takerGetsFunded.to_text(); + legOneOffer.taker_pays_funded = takerGetsFunded + .multiply(Utils.getOfferQuality(legOneOffer, this._currencyGets)) + .to_text(); + + if (legOneOffer.taker_gets_funded === legOneOffer.TakerGets.value) { + legOneOffer.is_fully_funded = true; + } +}; + +/** + * Set taker gets amount for a IOU:XRP offer. Also calculates taker pays + * using offer quality + * + * @param {Object} legOneOffer - IOU:XRP offer + * @param {Amount} takerGets + */ + +AutobridgeCalculator.prototype.setLegOneTakerGets = +function(legOneOffer, takerGets) { + assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid'); + assert(takerGets instanceof Amount, 'Taker gets funded is invalid'); + + var legOneQuality = Utils.getOfferQuality(legOneOffer, this._currencyGets); + + legOneOffer.TakerGets = takerGets.to_text(); + legOneOffer.TakerPays = takerGets.multiply(legOneQuality); +}; + +module.exports = AutobridgeCalculator; diff --git a/src/js/ripple/orderbook.js b/src/js/ripple/orderbook.js index e0fc7c75..87396f3f 100644 --- a/src/js/ripple/orderbook.js +++ b/src/js/ripple/orderbook.js @@ -19,6 +19,8 @@ var EventEmitter = require('events').EventEmitter; var Amount = require('./amount').Amount; var UInt160 = require('./uint160').UInt160; var Currency = require('./currency').Currency; +var AutobridgeCalculator = require('./autobridgecalculator'); +var OrderBookUtils = require('./orderbookutils'); var log = require('./log').internal.sub('orderbook'); function assertValidNumber(number, message) { @@ -35,21 +37,25 @@ function assertValidNumber(number, message) { * @param {String} orderbook key */ -function OrderBook(remote, getsC, getsI, paysC, paysI, key) { +function OrderBook(remote, + currencyGets, issuerGets, currencyPays, issuerPays, + key) { EventEmitter.call(this); var self = this; this._remote = remote; - this._currencyGets = Currency.from_json(getsC); - this._issuerGets = getsI; - this._currencyPays = Currency.from_json(paysC); - this._issuerPays = paysI; + this._currencyGets = Currency.from_json(currencyGets); + this._issuerGets = issuerGets; + this._currencyPays = Currency.from_json(currencyPays); + this._issuerPays = issuerPays; this._key = key; this._subscribed = false; this._shouldSubscribe = true; this._listeners = 0; - this._offers = [ ]; + this._offers = []; + this._offersAutobridged = []; + this._mergedOffers = []; this._offerCounts = {}; this._ownerFundsUnadjusted = {}; this._ownerFunds = {}; @@ -62,6 +68,37 @@ function OrderBook(remote, getsC, getsI, paysC, paysI, key) { // Transfer rate of the taker gets currency issuer this._issuerTransferRate = null; + // When orderbook is IOU/IOU, there will be IOU/XRP and XRP/IOU + // books that we must keep track of to compute autobridged offers + this._legOneBook = null; + this._legTwoBook = null; + + this._isAutobridgeable = !this._currencyGets.is_native() + && !this._currencyPays.is_native(); + + function computeAutobridgedOffersWrapper() { + self.computeAutobridgedOffers(); + self.mergeDirectAndAutobridgedBooks(); + } + + if (this._isAutobridgeable) { + this._legOneBook = remote.createOrderBook({ + currency_gets: 'XRP', + currency_pays: currencyPays, + issuer_pays: issuerPays + }); + + this._legOneBook.on('model', computeAutobridgedOffersWrapper); + + this._legTwoBook = remote.createOrderBook({ + currency_gets: currencyGets, + issuer_gets: issuerGets, + currency_pays: 'XRP' + }); + + this._legTwoBook.on('model', computeAutobridgedOffersWrapper); + } + function listenersModified(action, event) { // Automatically subscribe and unsubscribe to orderbook // on the basis of existing event listeners @@ -81,7 +118,7 @@ function OrderBook(remote, getsC, getsI, paysC, paysI, key) { } } - function updateFundedAmountsWrapper (transaction) { + function updateFundedAmountsWrapper(transaction) { self.updateFundedAmounts(transaction); } @@ -129,8 +166,6 @@ OrderBook.EVENTS = [ OrderBook.DEFAULT_TRANSFER_RATE = 1000000000; -OrderBook.IOU_SUFFIX = '/000/rrrrrrrrrrrrrrrrrrrrrhoLvTp'; - /** * Normalize offers from book_offers and transaction stream * @@ -247,7 +282,7 @@ OrderBook.prototype.requestOffers = function(callback) { self.setOffers(res.offers); self._synchronized = true; - self.emit('model', self._offers); + self.notifyDirectOffersChanged(); callback(null, self._offers); } @@ -269,6 +304,48 @@ OrderBook.prototype.requestOffers = function(callback) { return request; }; +/** + * Request transfer rate for this orderbook's issuer + * + * @param {Function} callback + */ + +OrderBook.prototype.requestTransferRate = function(callback) { + assert.strictEqual(typeof callback, 'function'); + + var self = this; + + if (this._currencyGets.is_native()) { + // Transfer rate is default for the native currency + this._issuerTransferRate = OrderBook.DEFAULT_TRANSFER_RATE; + + return callback(null, OrderBook.DEFAULT_TRANSFER_RATE); + } + + if (this._issuerTransferRate) { + // Transfer rate has already been cached + return callback(null, this._issuerTransferRate); + } + + function handleAccountInfo(err, info) { + if (err) { + return callback(err); + } + + // When transfer rate is not explicitly set on account, it implies the + // default transfer rate + self._issuerTransferRate = info.account_data.TransferRate || + OrderBook.DEFAULT_TRANSFER_RATE; + + callback(null, self._issuerTransferRate); + } + + this._remote.requestAccountInfo( + {account: this._issuerGets}, + handleAccountInfo + ); +}; + /** * Subscribe to transactions stream * @@ -317,6 +394,19 @@ OrderBook.prototype.subscribeTransactions = function(callback) { return request; }; +/** + * Handles notifying listeners that direct offers have changed. For autobridged + * books, an additional merge step is also performed + */ + +OrderBook.prototype.notifyDirectOffersChanged = function() { + if (this._isAutobridgeable) { + this.mergeDirectAndAutobridgedBooks(); + } else { + this.emit('model', this._offers); + } +}; + /** * Reset cached owner's funds, offer counts, and offer sums */ @@ -354,6 +444,27 @@ OrderBook.prototype.setOwnerFunds = function(account, fundedAmount) { this._ownerFunds[account] = this.applyTransferRate(fundedAmount); }; +/** + * Compute adjusted balance that would be left after issuer's transfer fee is + * deducted + * + * @param {String} balance + * @return {String} + */ + +OrderBook.prototype.applyTransferRate = function(balance) { + assert(!isNaN(balance), 'Balance is invalid'); + assertValidNumber(this._issuerTransferRate, 'Transfer rate is invalid'); + + var adjustedBalance = OrderBookUtils.normalizeAmount(balance) + .divide(this._issuerTransferRate) + .multiply(Amount.from_json(OrderBook.DEFAULT_TRANSFER_RATE)) + .to_json() + .value; + + return adjustedBalance; +}; + /** * Get owner's cached, transfer rate adjusted, funds * @@ -370,9 +481,7 @@ OrderBook.prototype.getOwnerFunds = function(account) { if (this._currencyGets.is_native()) { amount = Amount.from_json(this._ownerFunds[account]); } else { - amount = Amount.from_json( - this._ownerFunds[account] + OrderBook.IOU_SUFFIX - ); + amount = OrderBookUtils.normalizeAmount(this._ownerFunds[account]); } } @@ -459,6 +568,7 @@ OrderBook.prototype.decrementOwnerOfferCount = function(account) { OrderBook.prototype.addOwnerOfferTotal = function(account, amount) { assert(UInt160.is_valid(account), 'Account is invalid'); + var previousAmount = this.getOwnerOfferTotal(account); var currentAmount = previousAmount.add(Amount.from_json(amount)); @@ -478,6 +588,7 @@ OrderBook.prototype.addOwnerOfferTotal = function(account, amount) { OrderBook.prototype.subtractOwnerOfferTotal = function(account, amount) { assert(UInt160.is_valid(account), 'Account is invalid'); + var previousAmount = this.getOwnerOfferTotal(account); var newAmount = previousAmount.subtract(Amount.from_json(amount)); this._ownerOffersTotal[account] = newAmount; @@ -503,7 +614,7 @@ OrderBook.prototype.getOwnerOfferTotal = function(account) { if (this._currencyGets.is_native()) { amount = Amount.from_json('0'); } else { - amount = Amount.from_json('0' + OrderBook.IOU_SUFFIX); + amount = OrderBookUtils.normalizeAmount('0'); } } @@ -518,12 +629,14 @@ OrderBook.prototype.getOwnerOfferTotal = function(account) { */ OrderBook.prototype.resetOwnerOfferTotal = function(account) { + assert(UInt160.is_valid(account), 'Account is invalid'); + var amount; if (this._currencyGets.is_native()) { amount = Amount.from_json('0'); } else { - amount = Amount.from_json('0' + OrderBook.IOU_SUFFIX); + amount = OrderBookUtils.normalizeAmount('0'); } this._ownerOffersTotal[account] = amount; @@ -531,82 +644,6 @@ OrderBook.prototype.resetOwnerOfferTotal = function(account) { return amount; }; -/** - * Casts and returns offer's taker gets funded amount as a default IOU amount - * - * @param {Object} offer - * @return {Amount} - */ - -OrderBook.prototype.getOfferTakerGetsFunded = function(offer) { - assertValidNumber(offer.taker_gets_funded, 'Taker gets funded is invalid'); - - return Amount.from_json(offer.taker_gets_funded + OrderBook.IOU_SUFFIX); -}; - -/** - * Compute adjusted balance that would be left after issuer's transfer fee is - * deducted - * - * @param {String} balance - * @return {String} - */ - -OrderBook.prototype.applyTransferRate = function(balance) { - assert(!isNaN(balance), 'Balance is invalid'); - assertValidNumber(this._issuerTransferRate, 'Transfer rate is invalid'); - - var adjustedBalance = Amount.from_json(balance + OrderBook.IOU_SUFFIX) - .divide(this._issuerTransferRate) - .multiply(Amount.from_json(OrderBook.DEFAULT_TRANSFER_RATE)) - .to_json() - .value; - - return adjustedBalance; -}; - -/** - * Request transfer rate for this orderbook's issuer - * - * @param {Function} callback - */ - -OrderBook.prototype.requestTransferRate = function(callback) { - assert.strictEqual(typeof callback, 'function'); - - var self = this; - - if (this._currencyGets.is_native()) { - // Transfer rate is default for the native currency - this._issuerTransferRate = OrderBook.DEFAULT_TRANSFER_RATE; - - return callback(null, OrderBook.DEFAULT_TRANSFER_RATE); - } - - if (this._issuerTransferRate) { - // Transfer rate has already been cached - return callback(null, this._issuerTransferRate); - } - - function handleAccountInfo(err, info) { - if (err) { - return callback(err); - } - - // When transfer rate is not explicitly set on account, it implies the - // default transfer rate - self._issuerTransferRate = info.account_data.TransferRate || - OrderBook.DEFAULT_TRANSFER_RATE; - - callback(null, self._issuerTransferRate); - } - - this._remote.requestAccountInfo( - {account: this._issuerGets}, - handleAccountInfo - ); -}; - /** * Set funded amount on offer with its owner's cached funds * @@ -635,8 +672,9 @@ OrderBook.prototype.setOfferFundedAmount = function(offer) { } else if (previousOfferSum.compareTo(fundedAmount) < 0) { offer.taker_gets_funded = fundedAmount.subtract(previousOfferSum).to_text(); - var takerPaysFunded = this.getOfferQuality(offer).multiply( - this.getOfferTakerGetsFunded(offer) + var quality = OrderBookUtils.getOfferQuality(offer, this._currencyGets); + var takerPaysFunded = quality.multiply( + OrderBookUtils.getOfferTakerGetsFunded(offer) ); offer.taker_pays_funded = this._currencyPays.is_native() @@ -727,8 +765,8 @@ OrderBook.prototype.isBalanceChangeNode = function(node) { /** * Updates funded amounts/balances using modified balance nodes * - * Update owner funds using modified AccountRoot and RippleState nodes. - * Update funded amounts for offers in the orderbook using owner funds. + * Update owner funds using modified AccountRoot and RippleState nodes + * Update funded amounts for offers in the orderbook using owner funds * * @param {Object} transaction - transaction that holds meta nodes */ @@ -753,7 +791,7 @@ OrderBook.prototype.updateFundedAmounts = function(transaction) { entryType: this._currencyGets.is_native() ? 'AccountRoot' : 'RippleState' }); - _.each(affectedNodes, function (node) { + _.each(affectedNodes, function(node) { if (self.isBalanceChangeNode(node)) { var result = self.parseAccountBalanceFromNode(node); @@ -786,7 +824,7 @@ OrderBook.prototype.updateOwnerOffersFundedAmount = function(account) { this.resetOwnerOfferTotal(account); - _.each(this._offers, function (offer) { + _.each(this._offers, function(offer) { if (offer.Account !== account) { return; } @@ -798,14 +836,15 @@ OrderBook.prototype.updateOwnerOffersFundedAmount = function(account) { if (_.isString(offer.taker_gets_funded)) { // Offer is not new, so we should consider it for offer_changed and // offer_funds_changed events - previousFundedGets = self.getOfferTakerGetsFunded(offer); + previousFundedGets = OrderBookUtils.getOfferTakerGetsFunded(offer); } self.setOfferFundedAmount(offer); self.addOwnerOfferTotal(offer.Account, offer.TakerGets); + var takerGetsFunded = OrderBookUtils.getOfferTakerGetsFunded(offer); var areFundsChanged = previousFundedGets - && !self.getOfferTakerGetsFunded(offer).equals(previousFundedGets); + && !takerGetsFunded.equals(previousFundedGets); if (areFundsChanged) { self.emit('offer_changed', previousOffer, offer); @@ -894,7 +933,7 @@ OrderBook.prototype.notify = function(transaction) { _.each(affectedNodes, handleNode); this.emit('transaction', transaction); - this.emit('model', this._offers); + this.notifyDirectOffersChanged(); if (!takerGetsTotal.is_zero()) { this.emit('trade', takerPaysTotal, takerGetsTotal); } @@ -924,11 +963,14 @@ OrderBook.prototype.insertOffer = function(node) { // We're safe to calculate quality for newly created offers offer.quality = takerPays.divide(takerGets).to_text(); - var quality = this.getOfferQuality(offer); var originalLength = this._offers.length; for (var i = 0; i < originalLength; i++) { - var existingOfferQuality = this.getOfferQuality(this._offers[i]); + var quality = OrderBookUtils.getOfferQuality(offer, this._currencyGets); + var existingOfferQuality = OrderBookUtils.getOfferQuality( + this._offers[i], + this._currencyGets + ); if (quality.compareTo(existingOfferQuality) <= 0) { this._offers.splice(i, 0, offer); @@ -948,29 +990,6 @@ OrderBook.prototype.insertOffer = function(node) { this.emit('offer_added', offer); }; -/** - * Retrieve offer quality - * - * @param {Object} offer - */ - -OrderBook.prototype.getOfferQuality = function(offer) { - var amount; - - if (this._currencyGets.has_interest()) { - // XXX Should use Amount#from_quality - amount = Amount.from_json( - offer.TakerPays - ).ratio_human(offer.TakerGets, { - reference_date: new Date() - }); - } else { - amount = Amount.from_json(offer.quality + OrderBook.IOU_SUFFIX); - } - - return amount; -}; - /** * Convert any amount into default IOU * @@ -986,7 +1005,7 @@ OrderBook.prototype.normalizeAmount = function(currency, amountObj) { ? amountObj : amountObj.value; - return Amount.from_json(value + OrderBook.IOU_SUFFIX); + return OrderBookUtils.normalizeAmount(value); }; /** @@ -1060,13 +1079,13 @@ OrderBook.prototype.deleteOffer = function(node, isOfferCancel) { */ OrderBook.prototype.setOffers = function(offers) { - assert(Array.isArray(offers), ''); + assert(Array.isArray(offers), 'Offers is not an array'); var self = this; this.resetCache(); - var newOffers = _.map(offers, function (rawOffer) { + var newOffers = _.map(offers, function(rawOffer) { var offer = OrderBook.offerRewrite(rawOffer); if (offer.hasOwnProperty('owner_funds')) { @@ -1176,4 +1195,46 @@ OrderBook.prototype.is_valid = function() { ); }; +/** + * Compute autobridged offers for an IOU:IOU orderbook by merging offers from + * IOU:XRP and XRP:IOU books + */ + +OrderBook.prototype.computeAutobridgedOffers = function() { + assert(!this._currencyGets.is_native() && !this._currencyPays.is_native(), + 'Autobridging is only for IOU:IOU orderbooks'); + + var autobridgeCalculator = new AutobridgeCalculator( + this._currencyGets, + this._currencyPays, + this._legOneBook.getOffersSync(), + this._legTwoBook.getOffersSync() + ); + + this._offersAutobridged = autobridgeCalculator.calculate(); +}; + +/** + * Merge direct and autobridged offers into a combined orderbook + * + * @return [Array] + */ + +OrderBook.prototype.mergeDirectAndAutobridgedBooks = function() { + var self = this; + + this._mergedOffers = this._offers + .concat(this._offersAutobridged) + .sort(function(a, b) { + var aQuality = OrderBookUtils.getOfferQuality(a, self._currencyGets); + var bQuality = OrderBookUtils.getOfferQuality(b, self._currencyGets); + + return aQuality.compareTo(bQuality); + }); + + this.emit('model', this._mergedOffers); + + return this._mergedOffers; +}; + exports.OrderBook = OrderBook; diff --git a/src/js/ripple/orderbookutils.js b/src/js/ripple/orderbookutils.js new file mode 100644 index 00000000..8ab54fae --- /dev/null +++ b/src/js/ripple/orderbookutils.js @@ -0,0 +1,106 @@ +'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 = {}; + +function assertValidNumber(number, message) { + assert(!_.isNull(number) && !isNaN(number), message); +} + +/** + * Casts and returns offer's taker gets funded amount as a default IOU amount + * + * @param {Object} offer + * @return {Amount} + */ + +OrderBookUtils.getOfferTakerGetsFunded = function(offer) { + assertValidNumber(offer.taker_gets_funded, 'Taker gets funded is invalid'); + + return Amount.from_json(offer.taker_gets_funded + IOU_SUFFIX); +}; + +/** + * Casts and returns offer's taker pays funded amount as a default IOU amount + * + * @param {Object} offer + * @return {Amount} + */ + +OrderBookUtils.getOfferTakerPaysFunded = function(offer) { + assertValidNumber(offer.taker_pays_funded, 'Taker gets funded is invalid'); + + return Amount.from_json(offer.taker_pays_funded + IOU_SUFFIX); +}; + +/** + * Get offer taker gets amount + * + * @param {Object} offer + * + * @return {Amount} + */ + +OrderBookUtils.getOfferTakerGets = function(offer) { + assert(typeof offer, 'object', 'Offer is invalid'); + + return Amount.from_json(offer.TakerGets + IOU_SUFFIX); +}; + +/** + * Retrieve offer quality + * + * @param {Object} offer + * @param {Currency} currencyGets + */ + +OrderBookUtils.getOfferQuality = function(offer, currencyGets) { + var amount; + + if (currencyGets.has_interest()) { + // XXX Should use Amount#from_quality + amount = Amount.from_json( + offer.TakerPays + ).ratio_human(offer.TakerGets, { + reference_date: new Date() + }); + } else { + amount = Amount.from_json(offer.quality + IOU_SUFFIX); + } + + return amount; +}; + +/** + * Formats an offer quality amount to a hex that can be parsed by + * Amount.parse_quality + * + * @param {Amount} quality + * + * @return {String} + */ + +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); + + return so.to_hex(); +}; + +/** + * + */ + +OrderBookUtils.normalizeAmount = function(value) { + return Amount.from_json(value + IOU_SUFFIX); +}; + +module.exports = OrderBookUtils; diff --git a/src/js/ripple/serializedtypes.js b/src/js/ripple/serializedtypes.js index 7f4ecdff..640d90dc 100644 --- a/src/js/ripple/serializedtypes.js +++ b/src/js/ripple/serializedtypes.js @@ -90,7 +90,7 @@ function sort_fields(keys) { return keys.sort(sort_field_compare); } -SerializedType.serialize_varint = function (so, val) { +SerializedType.serialize_varint = function(so, val) { if (val < 0) { throw new Error('Variable integers are unsigned.'); } @@ -108,7 +108,7 @@ SerializedType.serialize_varint = function (so, val) { } }; -SerializedType.prototype.parse_varint = function (so) { +SerializedType.prototype.parse_varint = function(so) { var b1 = so.read(1)[0], b2, b3; var result; @@ -180,10 +180,10 @@ function readAndSum(so, bytes) { } var STInt8 = exports.Int8 = new SerializedType({ - serialize: function (so, val) { + serialize: function(so, val) { so.append(convertIntegerToByteArray(val, 1)); }, - parse: function (so) { + parse: function(so) { return readAndSum(so, 1); } }); @@ -274,10 +274,10 @@ function parse(so) { exports.parse = exports.parse_whatever = parse; var STInt16 = exports.Int16 = new SerializedType({ - serialize: function (so, val) { + serialize: function(so, val) { so.append(convertIntegerToByteArray(val, 2)); }, - parse: function (so) { + parse: function(so) { return readAndSum(so, 2); } }); @@ -285,10 +285,10 @@ var STInt16 = exports.Int16 = new SerializedType({ STInt16.id = 1; var STInt32 = exports.Int32 = new SerializedType({ - serialize: function (so, val) { + serialize: function(so, val) { so.append(convertIntegerToByteArray(val, 4)); }, - parse: function (so) { + parse: function(so) { return readAndSum(so, 4); } }); @@ -296,7 +296,7 @@ var STInt32 = exports.Int32 = new SerializedType({ STInt32.id = 2; var STInt64 = exports.Int64 = new SerializedType({ - serialize: function (so, val) { + serialize: function(so, val) { var bigNumObject; if (isNumber(val)) { @@ -320,7 +320,7 @@ var STInt64 = exports.Int64 = new SerializedType({ } serializeBits(so, bigNumObject.toBits(64), true); // noLength = true }, - parse: function (so) { + parse: function(so) { var bytes = so.read(8); return SJCL_BN.fromBits(sjcl.codec.bytes.toBits(bytes)); } @@ -329,14 +329,14 @@ var STInt64 = exports.Int64 = new SerializedType({ STInt64.id = 3; var STHash128 = exports.Hash128 = new SerializedType({ - serialize: function (so, val) { + serialize: function(so, val) { var hash = UInt128.from_json(val); if (!hash.is_valid()) { throw new Error('Invalid Hash128'); } serializeBits(so, hash.to_bits(), true); // noLength = true }, - parse: function (so) { + parse: function(so) { return UInt128.from_bytes(so.read(16)); } }); @@ -344,14 +344,14 @@ var STHash128 = exports.Hash128 = new SerializedType({ STHash128.id = 4; var STHash256 = exports.Hash256 = new SerializedType({ - serialize: function (so, val) { + serialize: function(so, val) { var hash = UInt256.from_json(val); if (!hash.is_valid()) { throw new Error('Invalid Hash256'); } serializeBits(so, hash.to_bits(), true); // noLength = true }, - parse: function (so) { + parse: function(so) { return UInt256.from_bytes(so.read(32)); } }); @@ -359,14 +359,14 @@ var STHash256 = exports.Hash256 = new SerializedType({ STHash256.id = 5; var STHash160 = exports.Hash160 = new SerializedType({ - serialize: function (so, val) { + serialize: function(so, val) { var hash = UInt160.from_json(val); if (!hash.is_valid()) { throw new Error('Invalid Hash160'); } serializeBits(so, hash.to_bits(), true); // noLength = true }, - parse: function (so) { + parse: function(so) { return UInt160.from_bytes(so.read(20)); } }); @@ -375,7 +375,7 @@ STHash160.id = 17; // Internal var STCurrency = new SerializedType({ - serialize: function (so, val) { + serialize: function(so, val) { var currencyData = val.to_bytes(); if (!currencyData) { @@ -385,7 +385,7 @@ var STCurrency = new SerializedType({ so.append(currencyData); }, - parse: function (so) { + parse: function(so) { var bytes = so.read(20); var currency = Currency.from_bytes(bytes); // XXX Disabled check. Theoretically, the Currency class should support any @@ -399,8 +399,51 @@ var STCurrency = new SerializedType({ } }); +/** + * Quality is encoded into 64 bits: + * (8 bits offset) (56 bits mantissa) + * + * Quality differs from Amount because it does not need the first two bits + * to represent non-native and non-negative + */ +exports.Quality = new SerializedType({ + serialize: function(so, val) { + var amount = Amount.from_json(val); + + if (!amount.is_valid()) { + throw new Error('Not a valid Amount object.'); + } + + var hi = 0, lo = 0; + var value = new BigNumber(amount.to_text()); + var offset = value.e - 15; + + if (!amount.is_zero()) { + // 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); + assert(mantissaHex.length <= 16, + 'Mantissa hex representation ' + mantissaHex + + ' exceeds the maximum length of 16'); + hi |= parseInt(mantissaHex.slice(0, -8), 16) & 0xffffff; + lo = parseInt(mantissaHex.slice(-8), 16); + } + + var valueBytes = sjcl.codec.bytes.fromBits([hi, lo]); + + so.append(valueBytes); + } +}); + +/* + * 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({ - serialize: function (so, val) { + serialize: function(so, val) { var amount = Amount.from_json(val); if (!amount.is_valid()) { @@ -477,7 +520,7 @@ var STAmount = exports.Amount = new SerializedType({ so.append(amount.issuer().to_bytes()); } }, - parse: function (so) { + parse: function(so) { var value_bytes = so.read(8); var is_zero = !(value_bytes[0] & 0x7f); @@ -519,14 +562,14 @@ var STAmount = exports.Amount = new SerializedType({ STAmount.id = 6; var STVL = exports.VariableLength = exports.VL = new SerializedType({ - serialize: function (so, val) { + serialize: function(so, val) { if (typeof val === 'string') { serializeHex(so, val); } else { throw new Error('Unknown datatype.'); } }, - parse: function (so) { + parse: function(so) { var len = this.parse_varint(so); return convertByteArrayToHex(so.read(len)); } @@ -535,14 +578,14 @@ var STVL = exports.VariableLength = exports.VL = new SerializedType({ STVL.id = 7; var STAccount = exports.Account = new SerializedType({ - serialize: function (so, val) { + serialize: function(so, val) { var account = UInt160.from_json(val); if (!account.is_valid()) { throw new Error('Invalid account!'); } serializeBits(so, account.to_bits()); }, - parse: function (so) { + parse: function(so) { var len = this.parse_varint(so); if (len !== 20) { @@ -568,7 +611,7 @@ var STPathSet = exports.PathSet = new SerializedType({ typeAccount: 0x01, typeCurrency: 0x10, typeIssuer: 0x20, - serialize: function (so, val) { + serialize: function(so, val) { for (var i = 0, l = val.length; i < l; i++) { // Boundary if (i) { @@ -609,7 +652,7 @@ var STPathSet = exports.PathSet = new SerializedType({ STInt8.serialize(so, this.typeEnd); }, - parse: function (so) { + parse: function(so) { // should return a list of lists: /* [ @@ -694,7 +737,7 @@ var STVector256 = exports.Vector256 = new SerializedType({ STHash256.serialize(so, val[i]); } }, - parse: function (so) { + parse: function(so) { var length = this.parse_varint(so); var output = []; // length is number of bytes not number of Hash256 @@ -712,7 +755,7 @@ exports.STMemo = new SerializedType({ serialize: function(so, val, no_marker) { var keys = []; - Object.keys(val).forEach(function (key) { + Object.keys(val).forEach(function(key) { // Ignore lowercase field names - they're non-serializable fields by // convention. if (key[0] === key[0].toLowerCase()) { @@ -754,6 +797,7 @@ exports.STMemo = new SerializedType({ output.parsed_memo_type = parsedType; } } 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 @@ -764,6 +808,7 @@ exports.STMemo = new SerializedType({ try { output.parsed_memo_format = convertHexToString(output.MemoFormat); } 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 @@ -783,6 +828,7 @@ exports.STMemo = new SerializedType({ output.parsed_memo_data = convertHexToString(output.MemoData); } } catch(e) { + // empty // we'll fail in case the content does not match what the MemoFormat // described // this is fine, we won't add the parsed_memo_data, the user has to @@ -797,10 +843,10 @@ exports.STMemo = new SerializedType({ }); var STObject = exports.Object = new SerializedType({ - serialize: function (so, val, no_marker) { + serialize: function(so, val, no_marker) { var keys = []; - Object.keys(val).forEach(function (key) { + Object.keys(val).forEach(function(key) { // Ignore lowercase field names - they're non-serializable fields by // convention. if (key[0] === key[0].toLowerCase()) { @@ -827,7 +873,7 @@ var STObject = exports.Object = new SerializedType({ } }, - parse: function (so) { + parse: function(so) { var output = {}; while (so.peek(1)[0] !== 0xe1) { var keyval = parse(so); @@ -841,7 +887,7 @@ var STObject = exports.Object = new SerializedType({ STObject.id = 14; var STArray = exports.Array = new SerializedType({ - serialize: function (so, val) { + serialize: function(so, val) { for (var i = 0, l = val.length; i < l; i++) { var keys = Object.keys(val[i]); @@ -859,7 +905,7 @@ var STArray = exports.Array = new SerializedType({ STInt8.serialize(so, 0xf1); }, - parse: function (so) { + parse: function(so) { var output = [ ]; while (so.peek(1)[0] !== 0xf1) { diff --git a/test/fixtures/orderbook.js b/test/fixtures/orderbook.js index 81cd5ec2..826103a7 100644 --- a/test/fixtures/orderbook.js +++ b/test/fixtures/orderbook.js @@ -24,7 +24,7 @@ module.exports.OTHER_LEDGER_INDEX = 'D3338DA77BA23122FB5647B74B53636AB54BE246D4B module.exports.TRANSFER_RATE = 1002000000; -module.exports.fiatOffers = function (options) { +module.exports.fiatOffers = function(options) { options = options || {}; _.defaults(options, { account_funds: '318.3643710638508', @@ -156,12 +156,12 @@ module.exports.NATIVE_OFFERS = [ PreviousTxnID: 'CD77500EF28984BFC123E8A257C10E44FF486EA8FC43E1356C42BD6DB853A602', PreviousTxnLgrSeq: 8265523, Sequence: 1139002, - TakerGets: { + TakerGets: '972251352', + TakerPays: { currency: 'USD', issuer: addresses.ISSUER, value: '4.9656112525' }, - TakerPays: '972251352', index: 'D3338DA77BA23122FB5647B74B53636AB54BE246D4B21707C9D6887DEB334252', owner_funds: '235.0194163432668', quality: '195796912.5171664' @@ -403,7 +403,119 @@ module.exports.DECIMAL_TAKER_PAYS_FUNDED_OFFERS = [ } ]; -module.exports.bookOffersResponse = function (options) { +module.exports.LEG_ONE_OFFERS = [ + { + Account: addresses.ACCOUNT, + BookDirectory: 'DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4D043654A0DBD245', + BookNode: '0000000000000000', + Flags: 0, + LedgerEntryType: 'Offer', + OwnerNode: '0000000000000078', + PreviousTxnID: '27723DCE3E6DB324DBCE9F0C9110352DBBC04DD6BEFE2A57C4E524FD215144C9', + PreviousTxnLgrSeq: 12024847, + Sequence: 14532890, + TakerGets: '31461561812', + TakerPays: { + currency: 'USD', + issuer: addresses.ISSUER, + value: '373.019921005' + }, + index: '7EEE980B0BD43C15504B9A89164D29EF02DBBD3807DA7936F51EA2CE3D0C6324', + owner_funds: '210586312936', + quality: '0.00000001185637010756165' + }, + { + Account: addresses.OTHER_ACCOUNT, + BookDirectory: 'DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4D043676B9DEA2FC', + BookNode: '0000000000000000', + Flags: 0, + LedgerEntryType: 'Offer', + OwnerNode: '0000000000000002', + PreviousTxnID: '1B36F7DE44C96FBDB50F8F80D24D3FA11454CB837BA4E4D667C92E01AE9225F5', + PreviousTxnLgrSeq: 12024788, + Sequence: 244399, + TakerGets: '25299728855', + TakerPays: { + currency: 'USD', + issuer: addresses.ISSUER, + value: '300' + }, + index: '5F8BDA3343CB792FA0DD55740F5827C5E050A287C96FDE4F7DFF548693420744', + owner_funds: '1291056089559', + quality: '0.00000001185783459259132' + }, + { + Account: addresses.THIRD_ACCOUNT, + BookDirectory: 'DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4D0437FF40E6F02A', + BookNode: '0000000000000000', + Expiration: 478636633, + Flags: 0, + LedgerEntryType: 'Offer', + OwnerNode: '0000000000000165', + PreviousTxnID: 'D42D81273BDC3ED611ED84DF07EA55E31703F4E05BC70CC12871715FCB58E160', + PreviousTxnLgrSeq: 12024847, + Sequence: 3858033, + TakerGets: '18189943147', + TakerPays: { + currency: 'USD', + issuer: addresses.ISSUER, + value: '216' + }, + index: 'FD5E66163DFE67919E64F31D506A8F3E94802E6A0FFEBE7A6FD40A2F1135EDD4', + owner_funds: '490342145233', + quality: '0.0000000118746935190737' + } +]; + +module.exports.LEG_TWO_OFFERS = [ + { + Account: addresses.FOURTH_ACCOUNT, + BookDirectory: 'DA36FDE1B8CE294B214BE4E4C958DAAF9C1F46DE1FCB44115D0A4929E095B160', + BookNode: '0000000000000000', + Flags: 0, + LedgerEntryType: 'Offer', + OwnerNode: '0000000000000003', + PreviousTxnID: '97A8D6B2135231363EC1B3B509DF052D481A0045684464948E6DF2C2B9FC1E64', + PreviousTxnLgrSeq: 12004045, + Sequence: 384, + TakerGets: { + currency: 'EUR', + issuer: addresses.ISSUER, + value: '17.07639524223001' + }, + TakerPays: '4943947661', + index: '5B00ACF35041983F070EAE2219C274D24A11D6FD6FE4306A4C72E7B769D4F914', + owner_funds: '36.40299530003982', + quality: '289519397.75' + }, + { + Account: addresses.FOURTH_ACCOUNT, + BookDirectory: 'DA36FDE1B8CE294B214BE4E4C958DAAF9C1F46DE1FCB44115E12B2D070B5DBE0', + BookNode: '0000000000000000', + Flags: 0, + LedgerEntryType: 'Offer', + OwnerNode: '0000000000000006', + PreviousTxnID: '425EBA467DD335602BAFBAB5329B1E7FC1ABB325AA5CD4495A5085860D09F2BE', + PreviousTxnLgrSeq: 11802828, + Sequence: 605, + TakerGets: { + currency: 'EUR', + issuer: addresses.ISSUER, + value: '19.99999999954904' + }, + TakerPays: '105263157889', + index: '8715E674302D446EBD520FF11B48A0F64822F4F9266D62544987223CA16EDBB1', + quality: '5263157894.7', + taker_gets_funded: { + currency: 'EUR', + issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', + value: '19.25393938854825' + }, + taker_pays_funded: '101336523096' + } +]; + +module.exports.bookOffersResponse = function(options) { options = options || {}; _.defaults(options, { account_funds: '2010.027702881682', diff --git a/test/orderbook-autobridge-test.js b/test/orderbook-autobridge-test.js new file mode 100644 index 00000000..8d07d037 --- /dev/null +++ b/test/orderbook-autobridge-test.js @@ -0,0 +1,753 @@ +/*eslint-disable max-len */ + +'use strict'; + +var _ = require('lodash'); +var assert = require('assert-diff'); +var Remote = require('ripple-lib').Remote; +var Currency = require('ripple-lib').Currency; +var addresses = require('./fixtures/addresses'); +var fixtures = require('./fixtures/orderbook'); + +describe('OrderBook Autobridging', function() { + this.timeout(0); + + it('Initialize IOU/IOU', function() { + var book = new Remote().createOrderBook({ + currency_gets: 'EUR', + issuer_gets: addresses.ISSUER, + currency_pays: 'USD', + issuer_pays: addresses.ISSUER + }); + + assert.deepEqual(book._legOneBook._currencyGets.to_hex(), Currency.from_json('XRP').to_hex()); + assert.deepEqual(book._legOneBook._currencyPays.to_hex(), Currency.from_json('USD').to_hex()); + assert.deepEqual(book._legTwoBook._currencyGets.to_hex(), Currency.from_json('EUR').to_hex()); + assert.deepEqual(book._legTwoBook._currencyPays.to_hex(), Currency.from_json('XRP').to_hex()); + }); + + it('Compute autobridged offers', function() { + var book = new Remote().createOrderBook({ + currency_gets: 'EUR', + issuer_gets: addresses.ISSUER, + currency_pays: 'USD', + issuer_pays: addresses.ISSUER + }); + + book._issuerTransferRate = 1000000000; + book._legOneBook._issuerTransferRate = 1000000000; + book._legTwoBook._issuerTransferRate = 1000000000; + + var legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); + var legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 1)); + + book._legOneBook.setOffers(legOneOffers); + book._legTwoBook.setOffers(legTwoOffers); + + book.computeAutobridgedOffers(); + + assert.strictEqual(book._offersAutobridged.length, 1); + + assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '17.07639524223001'); + assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '58.61727326122974'); + + assert.strictEqual(book._offersAutobridged[0].taker_gets_funded, '17.07639524223001'); + assert.strictEqual(book._offersAutobridged[0].taker_pays_funded, '58.61727326122974'); + + assert(book._offersAutobridged[0].autobridged); + }); + + it('Compute autobridged offers - leg one partially funded', function() { + var book = new Remote().createOrderBook({ + currency_gets: 'EUR', + issuer_gets: addresses.ISSUER, + currency_pays: 'USD', + issuer_pays: addresses.ISSUER + }); + + book._issuerTransferRate = 1000000000; + book._legOneBook._issuerTransferRate = 1000000000; + book._legTwoBook._issuerTransferRate = 1000000000; + + var legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); + var legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 1)); + + legOneOffers[0].owner_funds = '2105863129'; + + book._legOneBook.setOffers(legOneOffers); + book._legTwoBook.setOffers(legTwoOffers); + + book.computeAutobridgedOffers(); + + assert.strictEqual(book._offersAutobridged.length, 1); + + assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '7.273651248813431'); + assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '24.96789265329184'); + + assert(book._offersAutobridged[0].autobridged); + }); + + it('Compute autobridged offers - leg two partially funded', function() { + var book = new Remote().createOrderBook({ + currency_gets: 'EUR', + issuer_gets: addresses.ISSUER, + currency_pays: 'USD', + issuer_pays: addresses.ISSUER + }); + + book._issuerTransferRate = 1000000000; + book._legOneBook._issuerTransferRate = 1000000000; + book._legTwoBook._issuerTransferRate = 1000000000; + + var legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); + var legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 1)); + + legTwoOffers[0].owner_funds = '10'; + + book._legOneBook.setOffers(legOneOffers); + book._legTwoBook.setOffers(legTwoOffers); + + book.computeAutobridgedOffers(); + + assert.strictEqual(book._offersAutobridged.length, 1); + + assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '10'); + assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '34.32649132449533'); + + assert(book._offersAutobridged[0].autobridged); + }); + + it('Compute autobridged offers - leg two transfer rate', function() { + var book = new Remote().createOrderBook({ + currency_gets: 'EUR', + issuer_gets: addresses.ISSUER, + currency_pays: 'USD', + issuer_pays: addresses.ISSUER + }); + + book._issuerTransferRate = 1000000000; + book._legOneBook._issuerTransferRate = 1000000000; + book._legTwoBook._issuerTransferRate = 1002000000; + + var legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); + var legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 1)); + + legTwoOffers[0].owner_funds = '10'; + + book._legOneBook.setOffers(legOneOffers); + book._legTwoBook.setOffers(legTwoOffers); + + book.computeAutobridgedOffers(); + + assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '9.980039920159681'); + assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '34.25797537722665'); + + assert(book._offersAutobridged[0].autobridged); + }); + + it('Compute autobridged offers - taker funds < leg two in', function() { + var book = new Remote().createOrderBook({ + currency_gets: 'EUR', + issuer_gets: addresses.ISSUER, + currency_pays: 'USD', + issuer_pays: addresses.ISSUER + }); + + book._issuerTransferRate = 1000000000; + book._legOneBook._issuerTransferRate = 1000000000; + book._legTwoBook._issuerTransferRate = 1000000000; + + var legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); + var legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 1)); + + legOneOffers[0].owner_funds = '33461561812'; + + legTwoOffers[0].owner_funds = '360'; + legTwoOffers[0].TakerGets.value = '170.7639524223001'; + legTwoOffers[0].TakerPays = '49439476610'; + + book._legOneBook.setOffers(legOneOffers); + book._legTwoBook.setOffers(legTwoOffers); + + book.computeAutobridgedOffers(); + + assert.strictEqual(book._offersAutobridged.length, 1); + + assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '108.6682345172846'); + assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '373.019921005'); + + assert(book._offersAutobridged[0].autobridged); + }); + + it('Compute autobridged offers - leg one partially funded - owners equal', function() { + var book = new Remote().createOrderBook({ + currency_gets: 'EUR', + issuer_gets: addresses.ISSUER, + currency_pays: 'USD', + issuer_pays: addresses.ISSUER + }); + + book._issuerTransferRate = 1000000000; + book._legOneBook._issuerTransferRate = 1000000000; + book._legTwoBook._issuerTransferRate = 1000000000; + + var legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); + var legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 1)); + + legOneOffers[0].owner_funds = '2105863129'; + + legTwoOffers[0].Account = legOneOffers[0].Account; + + book._legOneBook.setOffers(legOneOffers); + book._legTwoBook.setOffers(legTwoOffers); + + book.computeAutobridgedOffers(); + + assert.strictEqual(book._offersAutobridged.length, 1); + + assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '17.07639524223001'); + assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '58.61727326122974'); + + assert(book._offersAutobridged[0].autobridged); + }); + + it('Compute autobridged offers - leg one partially funded - owners equal - leg two in > leg one out', function() { + var book = new Remote().createOrderBook({ + currency_gets: 'EUR', + issuer_gets: addresses.ISSUER, + currency_pays: 'USD', + issuer_pays: addresses.ISSUER + }); + + book._issuerTransferRate = 1000000000; + book._legOneBook._issuerTransferRate = 1000000000; + book._legTwoBook._issuerTransferRate = 1000000000; + + var legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); + var legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 1)); + + legOneOffers[0].owner_funds = '2105863129'; + + legTwoOffers[0].Account = legOneOffers[0].Account; + legTwoOffers[0].owner_funds = '360'; + legTwoOffers[0].TakerGets.value = '170.7639524223001'; + legTwoOffers[0].TakerPays = '49439476610'; + + book._legOneBook.setOffers(legOneOffers); + book._legTwoBook.setOffers(legTwoOffers); + + book.computeAutobridgedOffers(); + + assert.strictEqual(book._offersAutobridged.length, 1); + + assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '108.6682345172846'); + assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '373.0199210049999'); + + assert(book._offersAutobridged[0].autobridged); + }); + + it('Compute autobridged offers - leg one consumes leg two fully', function() { + var book = new Remote().createOrderBook({ + currency_gets: 'EUR', + issuer_gets: addresses.ISSUER, + currency_pays: 'USD', + issuer_pays: addresses.ISSUER + }); + + book._issuerTransferRate = 1000000000; + book._legOneBook._issuerTransferRate = 1000000000; + book._legTwoBook._issuerTransferRate = 1000000000; + + var legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); + var legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 2)); + + book._legOneBook.setOffers(legOneOffers); + book._legTwoBook.setOffers(legTwoOffers); + + book.computeAutobridgedOffers(); + + assert.strictEqual(book._offersAutobridged.length, 2); + + assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '17.07639524223001'); + assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '58.61727326122974'); + + assert(book._offersAutobridged[0].autobridged); + + assert.strictEqual(book._offersAutobridged[1].TakerGets.value, '5.038346688725268'); + assert.strictEqual(book._offersAutobridged[1].TakerPays.value, '314.4026477437702'); + + assert(book._offersAutobridged[1].autobridged); + }); + + it('Compute autobridged offers - leg two consumes first leg one offer fully', function() { + var book = new Remote().createOrderBook({ + currency_gets: 'EUR', + issuer_gets: addresses.ISSUER, + currency_pays: 'USD', + issuer_pays: addresses.ISSUER + }); + + book._issuerTransferRate = 1000000000; + book._legOneBook._issuerTransferRate = 1000000000; + book._legTwoBook._issuerTransferRate = 1000000000; + + var legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 2)); + var legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 1)); + + legTwoOffers[0].TakerGets.value = '170.7639524223001'; + legTwoOffers[0].TakerPays = '49439476610'; + legTwoOffers[0].owner_funds = '364.0299530003982'; + + book._legOneBook.setOffers(legOneOffers); + book._legTwoBook.setOffers(legTwoOffers); + + book.computeAutobridgedOffers(); + + assert.strictEqual(book._offersAutobridged.length, 2); + + assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '108.6682345172846'); + assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '373.019921005'); + + assert(book._offersAutobridged[0].autobridged); + + assert.strictEqual(book._offersAutobridged[1].TakerGets.value, '62.0957179050155'); + assert.strictEqual(book._offersAutobridged[1].TakerPays.value, '213.1791399943838'); + + assert(book._offersAutobridged[1].autobridged); + }); + + it('Compute autobridged offers - owners equal', function() { + var book = new Remote().createOrderBook({ + currency_gets: 'EUR', + issuer_gets: addresses.ISSUER, + currency_pays: 'USD', + issuer_pays: addresses.ISSUER + }); + + book._issuerTransferRate = 1000000000; + book._legOneBook._issuerTransferRate = 1000000000; + book._legTwoBook._issuerTransferRate = 1002000000; + + var legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); + var legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 2)); + + legOneOffers[0].owner_funds = '2105863129'; + legTwoOffers[1].owner_funds = '19.32660005780981'; + + legTwoOffers[0].Account = legOneOffers[0].Account; + + book._legOneBook.setOffers(legOneOffers); + book._legTwoBook.setOffers(legTwoOffers); + + book.computeAutobridgedOffers(); + + assert.strictEqual(book._offersAutobridged.length, 2); + + assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '17.07639524223001'); + assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '58.61727326122974'); + + assert(book._offersAutobridged[0].autobridged); + + assert.strictEqual(book._offersAutobridged[1].TakerGets.value, '0.4001139945128008'); + assert.strictEqual(book._offersAutobridged[1].TakerPays.value, '24.96789265329184'); + + assert(book._offersAutobridged[1].autobridged); + }); + + it('Compute autobridged offers - owners equal - leg one overfunded', function() { + var book = new Remote().createOrderBook({ + currency_gets: 'EUR', + issuer_gets: addresses.ISSUER, + currency_pays: 'USD', + issuer_pays: addresses.ISSUER + }); + + book._issuerTransferRate = 1000000000; + book._legOneBook._issuerTransferRate = 1000000000; + book._legTwoBook._issuerTransferRate = 1002000000; + + var legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); + var legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 2)); + + legOneOffers[0].owner_funds = '41461561812'; + + legTwoOffers[0].Account = legOneOffers[0].Account; + + legTwoOffers[1].owner_funds = '30'; + + book._legOneBook.setOffers(legOneOffers); + book._legTwoBook.setOffers(legTwoOffers); + + book.computeAutobridgedOffers(); + + assert.strictEqual(book._offersAutobridged.length, 2); + + assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '17.07639524223001'); + assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '58.61727326122974'); + + assert(book._offersAutobridged[0].autobridged); + + assert.strictEqual(book._offersAutobridged[1].TakerGets.value, '5.038346688725268'); + assert.strictEqual(book._offersAutobridged[1].TakerPays.value, '314.4026477437702'); + + assert(book._offersAutobridged[1].autobridged); + }); + + it('Compute autobridged offers - TakerPays < Quality * TakerGets', function() { + var book = new Remote().createOrderBook({ + currency_gets: 'EUR', + issuer_gets: addresses.ISSUER, + currency_pays: 'USD', + issuer_pays: addresses.ISSUER + }); + + book._issuerTransferRate = 1000000000; + book._legOneBook._issuerTransferRate = 1000000000; + book._legTwoBook._issuerTransferRate = 1000000000; + + book._legOneBook.setOffers([ + { + Account: addresses.ACCOUNT, + TakerGets: '75', + TakerPays: { + value: '50', + issuer: addresses.ISSUER, + currency: 'USD' + }, + owner_funds: '50', + quality: '1' + } + ]); + + book._legTwoBook.setOffers([ + { + Account: addresses.ACCOUNT, + TakerGets: { + value: '90', + issuer: addresses.ISSUER, + currency: 'EUR' + }, + TakerPays: '90', + owner_funds: '150', + quality: '1' + } + ]); + + book.computeAutobridgedOffers(); + + assert.strictEqual(book._offersAutobridged.length, 1); + + assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '75'); + assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '75'); + + assert(book._offersAutobridged[0].autobridged); + }); + + it('Compute autobridged offers - update funded amount', function() { + var book = new Remote().createOrderBook({ + currency_gets: 'EUR', + issuer_gets: addresses.ISSUER, + currency_pays: 'USD', + issuer_pays: addresses.ISSUER + }); + + book._issuerTransferRate = 1000000000; + book._legOneBook._issuerTransferRate = 1000000000; + book._legTwoBook._issuerTransferRate = 1000000000; + + book._legOneBook.setOffers([ + { + Account: addresses.ACCOUNT, + TakerGets: '100', + TakerPays: { + value: '100', + issuer: addresses.ISSUER, + currency: 'USD' + }, + owner_funds: '50', + quality: '1' + }, + { + Account: addresses.ACCOUNT, + TakerGets: '50', + TakerPays: { + value: '100', + issuer: addresses.ISSUER, + currency: 'USD' + }, + quality: '2' + } + ]); + + book._legTwoBook.setOffers([ + { + Account: addresses.ACCOUNT, + TakerGets: { + value: '90', + issuer: addresses.ISSUER, + currency: 'EUR' + }, + TakerPays: '90', + owner_funds: '150', + quality: '1' + }, + { + Account: addresses.OTHER_ACCOUNT, + TakerGets: { + value: '30', + issuer: addresses.ISSUER, + currency: 'EUR' + }, + TakerPays: '60', + owner_funds: '70', + quality: '2' + } + ]); + + book.computeAutobridgedOffers(); + + assert.strictEqual(book._offersAutobridged.length, 3); + + assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '90'); + assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '90'); + + assert(book._offersAutobridged[0].autobridged); + + assert.strictEqual(book._offersAutobridged[1].TakerGets.value, '5'); + assert.strictEqual(book._offersAutobridged[1].TakerPays.value, '10'); + + assert(book._offersAutobridged[1].autobridged); + + assert.strictEqual(book._offersAutobridged[2].TakerGets.value, '20'); + assert.strictEqual(book._offersAutobridged[2].TakerPays.value, '80'); + + assert(book._offersAutobridged[2].autobridged); + }); + + it('Compute autobridged offers - update funded amount - owners equal', function() { + var book = new Remote().createOrderBook({ + currency_gets: 'EUR', + issuer_gets: addresses.ISSUER, + currency_pays: 'USD', + issuer_pays: addresses.ISSUER + }); + + book._issuerTransferRate = 1000000000; + book._legOneBook._issuerTransferRate = 1000000000; + book._legTwoBook._issuerTransferRate = 1000000000; + + book._legOneBook.setOffers([ + { + Account: addresses.ACCOUNT, + TakerGets: '100', + TakerPays: { + value: '100', + issuer: addresses.ISSUER, + currency: 'USD' + }, + owner_funds: '50', + quality: '1' + }, + { + Account: addresses.ACCOUNT, + TakerGets: '20', + TakerPays: { + value: '100', + issuer: addresses.ISSUER, + currency: 'USD' + }, + quality: '5' + } + ]); + + book._legTwoBook.setOffers([ + { + Account: addresses.ACCOUNT, + TakerGets: { + value: '90', + issuer: addresses.ISSUER, + currency: 'EUR' + }, + TakerPays: '90', + owner_funds: '150', + quality: '1' + }, + { + Account: addresses.OTHER_ACCOUNT, + TakerGets: { + value: '30', + issuer: addresses.ISSUER, + currency: 'EUR' + }, + TakerPays: '60', + owner_funds: '70', + quality: '2' + } + ]); + + book.computeAutobridgedOffers(); + + assert.strictEqual(book._offersAutobridged.length, 3); + + assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '90'); + assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '90'); + + assert(book._offersAutobridged[0].autobridged); + + assert.strictEqual(book._offersAutobridged[1].TakerGets.value, '5'); + assert.strictEqual(book._offersAutobridged[1].TakerPays.value, '10'); + + assert(book._offersAutobridged[1].autobridged); + + assert.strictEqual(book._offersAutobridged[2].TakerGets.value, '10'); + assert.strictEqual(book._offersAutobridged[2].TakerPays.value, '100'); + + assert(book._offersAutobridged[2].autobridged); + }); + + it('Compute autobridged offers - update funded amount - first two owners equal', function() { + var book = new Remote().createOrderBook({ + currency_gets: 'EUR', + issuer_gets: addresses.ISSUER, + currency_pays: 'USD', + issuer_pays: addresses.ISSUER + }); + + book._issuerTransferRate = 1000000000; + book._legOneBook._issuerTransferRate = 1000000000; + book._legTwoBook._issuerTransferRate = 1000000000; + + book._legOneBook.setOffers([ + { + Account: addresses.ACCOUNT, + TakerGets: '100', + TakerPays: { + value: '100', + issuer: addresses.ISSUER, + currency: 'USD' + }, + owner_funds: '50', + quality: '1' + }, + { + Account: addresses.ACCOUNT, + TakerGets: '100', + TakerPays: { + value: '200', + issuer: addresses.ISSUER, + currency: 'USD' + }, + quality: '2' + } + ]); + + book._legTwoBook.setOffers([ + { + Account: addresses.ACCOUNT, + TakerGets: { + value: '90', + issuer: addresses.ISSUER, + currency: 'EUR' + }, + TakerPays: '90', + owner_funds: '150', + quality: '1' + }, + { + Account: addresses.ACCOUNT, + TakerGets: { + value: '30', + issuer: addresses.ISSUER, + currency: 'EUR' + }, + TakerPays: '60', + quality: '2' + }, + { + Account: addresses.OTHER_ACCOUNT, + TakerGets: { + value: '20', + issuer: addresses.ISSUER, + currency: 'EUR' + }, + TakerPays: '40', + owner_funds: '70', + quality: '2' + } + ]); + + book.computeAutobridgedOffers(); + + assert.strictEqual(book._offersAutobridged.length, 4); + + assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '90'); + assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '90'); + + assert(book._offersAutobridged[0].autobridged); + + assert.strictEqual(book._offersAutobridged[1].TakerGets.value, '5'); + assert.strictEqual(book._offersAutobridged[1].TakerPays.value, '10'); + + assert(book._offersAutobridged[1].autobridged); + + assert.strictEqual(book._offersAutobridged[2].TakerGets.value, '25'); + assert.strictEqual(book._offersAutobridged[2].TakerPays.value, '100'); + + assert(book._offersAutobridged[2].autobridged); + + assert.strictEqual(book._offersAutobridged[3].TakerGets.value, '20'); + assert.strictEqual(book._offersAutobridged[3].TakerPays.value, '80'); + + assert(book._offersAutobridged[3].autobridged); + }); + + it('Compute autobridged offers - unfunded offer - owners equal', function() { + var book = new Remote().createOrderBook({ + currency_gets: 'EUR', + issuer_gets: addresses.ISSUER, + currency_pays: 'USD', + issuer_pays: addresses.ISSUER + }); + + book._issuerTransferRate = 1000000000; + book._legOneBook._issuerTransferRate = 1000000000; + book._legTwoBook._issuerTransferRate = 1000000000; + + book._legOneBook.setOffers([ + { + Account: addresses.ACCOUNT, + TakerGets: '75', + TakerPays: { + value: '75', + issuer: addresses.ISSUER, + currency: 'USD' + }, + owner_funds: '0', + quality: '1' + } + ]); + + book._legTwoBook.setOffers([ + { + Account: addresses.ACCOUNT, + TakerGets: { + value: '90', + issuer: addresses.ISSUER, + currency: 'EUR' + }, + TakerPays: '90', + owner_funds: '150', + quality: '1' + } + ]); + + book.computeAutobridgedOffers(); + + assert.strictEqual(book._offersAutobridged.length, 1); + + assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '75'); + assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '75'); + + assert(book._offersAutobridged[0].autobridged); + }); +}); diff --git a/test/orderbook-test.js b/test/orderbook-test.js index 8108b325..f37f68ad 100644 --- a/test/orderbook-test.js +++ b/test/orderbook-test.js @@ -2119,7 +2119,7 @@ describe('OrderBook', function() { assert.strictEqual(book._offers[2].taker_pays_funded, '101533965'); }); - it('Insert offer - worst quality - insufficient funds for all orders', function () { + it('Insert offer - worst quality - insufficient funds for all orders', function() { var remote = new Remote(); var book = remote.createOrderBook({ currency_gets: 'USD', @@ -2193,159 +2193,6 @@ describe('OrderBook', function() { assert.strictEqual(book._offers[2].taker_pays_funded, '0'); }); - it('Request offers', function(done) { - var remote = new Remote(); - - var offers = { - offers: fixtures.REQUEST_OFFERS - }; - - remote.request = function(request) { - switch (request.message.command) { - case 'book_offers': - assert.deepEqual(request.message, { - command: 'book_offers', - id: undefined, - taker_gets: { - currency: '0000000000000000000000004254430000000000', - issuer: addresses.ISSUER - }, - taker_pays: { - currency: '0000000000000000000000005553440000000000', - issuer: addresses.ISSUER - }, - taker: 'rrrrrrrrrrrrrrrrrrrrBZbvji' - }); - - setImmediate(function() { - request.emit('success', offers); - }); - break; - } - }; - - var book = remote.createOrderBook({ - currency_gets: 'BTC', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book._issuerTransferRate = 1002000000; - - var expected = [ - { - Account: addresses.ACCOUNT, - BookDirectory: '6EAB7C172DEFA430DBFAD120FDC373B5F5AF8B191649EC985711A3A4254F5000', - BookNode: '0000000000000000', - Flags: 131072, - LedgerEntryType: 'Offer', - OwnerNode: '0000000000000000', - Sequence: 195, - TakerGets: { - currency: 'BTC', - issuer: addresses.ISSUER, - value: '0.1129232560043778' - }, - TakerPays: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '56.06639660617357' - }, - index: 'B6BC3B0F87976370EE11F5575593FE63AA5DC1D602830DC96F04B2D597F044BF', - owner_funds: '0.1129267125000245', - taker_gets_funded: '0.112701309880264', - taker_pays_funded: '55.95620035555106', - is_fully_funded: false, - quality: '496.4999999999999' - }, - { - Account: addresses.OTHER_ACCOUNT, - BookDirectory: '6EAB7C172DEFA430DBFAD120FDC373B5F5AF8B191649EC985711B6D8C62EF414', - BookNode: '0000000000000000', - Expiration: 461498565, - Flags: 131072, - LedgerEntryType: 'Offer', - OwnerNode: '0000000000000144', - Sequence: 29354, - TakerGets: { - currency: 'BTC', - issuer: addresses.ISSUER, - value: '0.2' - }, - TakerPays: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '99.72233516476456' - }, - index: 'A437D85DF80D250F79308F2B613CF5391C7CF8EE9099BC4E553942651CD9FA86', - owner_funds: '0.950363009783092', - is_fully_funded: true, - taker_gets_funded: '0.2', - taker_pays_funded: '99.72233516476456', - quality: '498.6116758238228' - }, - { - Account: addresses.THIRD_ACCOUNT, - BookDirectory: '6EAB7C172DEFA430DBFAD120FDC373B5F5AF8B191649EC985711B6D8C62EF414', - BookNode: '0000000000000000', - Expiration: 461498565, - Flags: 131072, - LedgerEntryType: 'Offer', - OwnerNode: '0000000000000144', - Sequence: 29356, - TakerGets: { - currency: 'BTC', - issuer: addresses.ISSUER, - value: '0.5' - }, - TakerPays: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '99.72233516476456' - }, - index: 'A437D85DF80D250F79308F2B613CF5391C7CF8EE9099BC4E553942651CD9FA86', - owner_funds: '0.950363009783092', - is_fully_funded: true, - taker_gets_funded: '0.5', - taker_pays_funded: '99.72233516476456', - quality: '498.6116758238228' - }, - { - Account: addresses.THIRD_ACCOUNT, - BookDirectory: '6EAB7C172DEFA430DBFAD120FDC373B5F5AF8B191649EC985711B6D8C62EF414', - BookNode: '0000000000000000', - Expiration: 461498565, - Flags: 131078, - LedgerEntryType: 'Offer', - OwnerNode: '0000000000000144', - Sequence: 29354, - TakerGets: { - currency: 'BTC', - issuer: addresses.ISSUER, - value: '0.5' - }, - TakerPays: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '99.72233516476456' - }, - index: 'A437D85DF80D250F79308F2B613CF5391C7CF8EE9099BC4E553942651CD9FA86', - owner_funds: '0.950363009783092', - is_fully_funded: false, - taker_gets_funded: '0.4484660776278363', - taker_pays_funded: '89.44416900646082', - quality: '199.4446703295291' - } - ]; - - book.on('model', function(model) { - assert.deepEqual(model, expected); - assert.strictEqual(book._synchronized, true); - done(); - }); - }); - it('Request offers - native currency', function(done) { var remote = new Remote(); diff --git a/test/serializedtypes-test.js b/test/serializedtypes-test.js index 4fa4cad0..34a93d2f 100644 --- a/test/serializedtypes-test.js +++ b/test/serializedtypes-test.js @@ -1,220 +1,225 @@ -var assert = require('assert'); +/*eslint-disable max-len */ + +'use strict'; + +var assert = require('assert'); +var BigNumber = require('bignumber.js'); var SerializedObject = require('ripple-lib').SerializedObject; -var types = require('ripple-lib').types; -var Amount = require('ripple-lib').Amount; -var sjcl = require('ripple-lib').sjcl; +var types = require('ripple-lib').types; +var Amount = require('ripple-lib').Amount; +var sjcl = require('ripple-lib').sjcl; describe('Serialized types', function() { describe('Int8', function() { - it('Serialize 0', function () { + it('Serialize 0', function() { var so = new SerializedObject(); types.Int8.serialize(so, 0); assert.strictEqual(so.to_hex(), '00'); }); - it('Serialize 123', function () { + it('Serialize 123', function() { var so = new SerializedObject(); types.Int8.serialize(so, 123); assert.strictEqual(so.to_hex(), '7B'); }); - it('Serialize 255', function () { + it('Serialize 255', function() { var so = new SerializedObject(); types.Int8.serialize(so, 255); assert.strictEqual(so.to_hex(), 'FF'); }); - it('Fail to serialize 256', function () { + it('Fail to serialize 256', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int8.serialize(so, 256); }); }); - it('Fail to serialize -1', function () { + it('Fail to serialize -1', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int8.serialize(so, -1); }); }); - it('Serialize 5.5 (should floor)', function () { + it('Serialize 5.5 (should floor)', function() { var so = new SerializedObject(); types.Int8.serialize(so, 5.5); assert.strictEqual(so.to_hex(), '05'); }); - it('Serialize 255.9 (should floor)', function () { + it('Serialize 255.9 (should floor)', function() { var so = new SerializedObject(); types.Int8.serialize(so, 255.9); assert.strictEqual(so.to_hex(), 'FF'); }); - it('Fail to serialize null', function () { + it('Fail to serialize null', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int8.serialize(so, null); }); }); - it('Fail to serialize "bla"', function () { + it('Fail to serialize "bla"', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int8.serialize(so, 'bla'); }); }); - it('Fail to serialize {}', function () { + it('Fail to serialize {}', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int8.serialize(so, {}); }); }); }); describe('Int16', function() { - it('Serialize 0', function () { + it('Serialize 0', function() { var so = new SerializedObject(); types.Int16.serialize(so, 0); assert.strictEqual(so.to_hex(), '0000'); }); - it('Serialize 123', function () { + it('Serialize 123', function() { var so = new SerializedObject(); types.Int16.serialize(so, 123); assert.strictEqual(so.to_hex(), '007B'); }); - it('Serialize 255', function () { + it('Serialize 255', function() { var so = new SerializedObject(); types.Int16.serialize(so, 255); assert.strictEqual(so.to_hex(), '00FF'); }); - it('Serialize 256', function () { + it('Serialize 256', function() { var so = new SerializedObject(); types.Int16.serialize(so, 256); assert.strictEqual(so.to_hex(), '0100'); }); - it('Serialize 65535', function () { + it('Serialize 65535', function() { var so = new SerializedObject(); types.Int16.serialize(so, 65535); assert.strictEqual(so.to_hex(), 'FFFF'); }); - it('Fail to serialize 65536', function () { + it('Fail to serialize 65536', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int8.serialize(so, 65536); }); }); - it('Fail to serialize -1', function () { + it('Fail to serialize -1', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int16.serialize(so, -1); }); }); - it('Serialize 123.5 (should floor)', function () { + it('Serialize 123.5 (should floor)', function() { var so = new SerializedObject(); types.Int16.serialize(so, 123.5); assert.strictEqual(so.to_hex(), '007B'); }); - it('Serialize 65535.5 (should floor)', function () { + it('Serialize 65535.5 (should floor)', function() { var so = new SerializedObject(); types.Int16.serialize(so, 65535.5); assert.strictEqual(so.to_hex(), 'FFFF'); }); - it('Fail to serialize null', function () { + it('Fail to serialize null', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int16.serialize(so, null); }); }); - it('Fail to serialize "bla"', function () { + it('Fail to serialize "bla"', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int16.serialize(so, 'bla'); }); }); - it('Fail to serialize {}', function () { + it('Fail to serialize {}', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int16.serialize(so, {}); }); }); }); describe('Int32', function() { - it('Serialize 0', function () { + it('Serialize 0', function() { var so = new SerializedObject(); types.Int32.serialize(so, 0); assert.strictEqual(so.to_hex(), '00000000'); }); - it('Serialize 123', function () { + it('Serialize 123', function() { var so = new SerializedObject(); types.Int32.serialize(so, 123); assert.strictEqual(so.to_hex(), '0000007B'); }); - it('Serialize 255', function () { + it('Serialize 255', function() { var so = new SerializedObject(); types.Int32.serialize(so, 255); assert.strictEqual(so.to_hex(), '000000FF'); }); - it('Serialize 256', function () { + it('Serialize 256', function() { var so = new SerializedObject(); types.Int32.serialize(so, 256); assert.strictEqual(so.to_hex(), '00000100'); }); - it('Serialize 0xF0F0F0F0', function () { + it('Serialize 0xF0F0F0F0', function() { var so = new SerializedObject(); types.Int32.serialize(so, 0xF0F0F0F0); assert.strictEqual(so.to_hex(), 'F0F0F0F0'); }); - it('Serialize 0xFFFFFFFF', function () { + it('Serialize 0xFFFFFFFF', function() { var so = new SerializedObject(); types.Int32.serialize(so, 0xFFFFFFFF); assert.strictEqual(so.to_hex(), 'FFFFFFFF'); }); - it('Fail to serialize 0x100000000', function () { + it('Fail to serialize 0x100000000', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int8.serialize(so, 0x100000000); }); }); - it('Fail to serialize -1', function () { + it('Fail to serialize -1', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int32.serialize(so, -1); }); }); - it('Serialize 123.5 (should floor)', function () { + it('Serialize 123.5 (should floor)', function() { var so = new SerializedObject(); types.Int32.serialize(so, 123.5); assert.strictEqual(so.to_hex(), '0000007B'); }); - it('Serialize 4294967295.5 (should floor)', function () { + it('Serialize 4294967295.5 (should floor)', function() { var so = new SerializedObject(); types.Int32.serialize(so, 4294967295.5); assert.strictEqual(so.to_hex(), 'FFFFFFFF'); }); - it('Fail to serialize null', function () { + it('Fail to serialize null', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int32.serialize(so, null); }); }); - it('Fail to serialize "bla"', function () { + it('Fail to serialize "bla"', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int32.serialize(so, 'bla'); }); }); - it('Fail to serialize {}', function () { + it('Fail to serialize {}', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int32.serialize(so, {}); }); }); - it('Parse 0', function () { + it('Parse 0', function() { var val = '00000000'; var so = new SerializedObject(val); var num = types.Int32.parse(so); assert.strictEqual(num, parseInt(val, 16)); }); - it('Parse 1', function () { + it('Parse 1', function() { var val = '00000001'; var so = new SerializedObject(val); var num = types.Int32.parse(so); assert.strictEqual(num, parseInt(val, 16)); }); - it('Parse UINT32_MAX', function () { + it('Parse UINT32_MAX', function() { var val = 'FFFFFFFF'; var so = new SerializedObject(val); var num = types.Int32.parse(so); @@ -223,159 +228,160 @@ describe('Serialized types', function() { }); describe('Int64', function() { - it('Serialize 0', function () { + it('Serialize 0', function() { var so = new SerializedObject(); types.Int64.serialize(so, 0); assert.strictEqual(so.to_hex(), '0000000000000000'); }); - it('Serialize 123', function () { + it('Serialize 123', function() { var so = new SerializedObject(); types.Int64.serialize(so, 123); assert.strictEqual(so.to_hex(), '000000000000007B'); }); - it('Serialize 255', function () { + it('Serialize 255', function() { var so = new SerializedObject(); types.Int64.serialize(so, 255); assert.strictEqual(so.to_hex(), '00000000000000FF'); }); - it('Serialize 256', function () { + it('Serialize 256', function() { var so = new SerializedObject(); types.Int64.serialize(so, 256); assert.strictEqual(so.to_hex(), '0000000000000100'); }); - it('Serialize 0xF0F0F0F0', function () { + it('Serialize 0xF0F0F0F0', function() { var so = new SerializedObject(); types.Int64.serialize(so, 0xF0F0F0F0); assert.strictEqual(so.to_hex(), '00000000F0F0F0F0'); }); - it('Serialize 0xFFFFFFFF', function () { + it('Serialize 0xFFFFFFFF', function() { var so = new SerializedObject(); types.Int64.serialize(so, 0xFFFFFFFF); assert.strictEqual(so.to_hex(), '00000000FFFFFFFF'); }); - it('Serialize 0x100000000', function () { + it('Serialize 0x100000000', function() { var so = new SerializedObject(); types.Int64.serialize(so, 0x100000000); assert.strictEqual(so.to_hex(), '0000000100000000'); }); - it('Fail to serialize 0x100000000', function () { + it('Fail to serialize 0x100000000', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int8.serialize(so, 0x100000000); }); }); - it('Fail to serialize -1', function () { + it('Fail to serialize -1', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int64.serialize(so, -1); }); }); - it('Serialize 123.5 (should floor)', function () { + it('Serialize 123.5 (should floor)', function() { var so = new SerializedObject(); types.Int64.serialize(so, 123.5); assert.strictEqual(so.to_hex(), '000000000000007B'); }); - it('Serialize 4294967295.5 (should floor)', function () { + it('Serialize 4294967295.5 (should floor)', function() { var so = new SerializedObject(); types.Int64.serialize(so, 4294967295.5); assert.strictEqual(so.to_hex(), '00000000FFFFFFFF'); }); - it('Does not get confused when the high bit is set', function () { + it('Does not get confused when the high bit is set', function() { var so = new SerializedObject(); - types.Int64.serialize(so, "8B2386F26F8E232B"); + types.Int64.serialize(so, '8B2386F26F8E232B'); assert.strictEqual(so.to_hex(), '8B2386F26F8E232B'); - var so = new SerializedObject("8B2386F26F8E232B"); + so = new SerializedObject('8B2386F26F8E232B'); var num = types.Int64.parse(so); // We get a positive number assert.strictEqual(num.toString(), '0x8b2386f26f8e232b'); }); - it('Serialize "0123456789ABCDEF"', function () { + it('Serialize "0123456789ABCDEF"', function() { var so = new SerializedObject(); types.Int64.serialize(so, '0123456789ABCDEF'); assert.strictEqual(so.to_hex(), '0123456789ABCDEF'); }); - it('Serialize "F0E1D2C3B4A59687"', function () { + it('Serialize "F0E1D2C3B4A59687"', function() { var so = new SerializedObject(); types.Int64.serialize(so, 'F0E1D2C3B4A59687'); assert.strictEqual(so.to_hex(), 'F0E1D2C3B4A59687'); }); - it('Serialize bn("FFEEDDCCBBAA9988")', function () { + it('Serialize bn("FFEEDDCCBBAA9988")', function() { var so = new SerializedObject(); - types.Int64.serialize(so, new sjcl.bn('FFEEDDCCBBAA9988', 16)); + var BN = sjcl.bn; + types.Int64.serialize(so, new BN('FFEEDDCCBBAA9988', 16)); assert.strictEqual(so.to_hex(), 'FFEEDDCCBBAA9988'); }); - it('Fail to serialize BigNumber("-1")', function () { + it('Fail to serialize BigNumber("-1")', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int64.serialize(so, new BigNumber('-1', 10)); }); }); - it('Fail to serialize "10000000000000000"', function () { + it('Fail to serialize "10000000000000000"', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int64.serialize(so, '10000000000000000'); }); }); - it('Fail to serialize "110000000000000000"', function () { + it('Fail to serialize "110000000000000000"', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int64.serialize(so, '110000000000000000'); }); }); - it('Fail to serialize null', function () { + it('Fail to serialize null', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int64.serialize(so, null); }); }); - it('Fail to serialize "bla"', function () { + it('Fail to serialize "bla"', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int64.serialize(so, 'bla'); }); }); - it('Fail to serialize {}', function () { + it('Fail to serialize {}', function() { var so = new SerializedObject(); - assert.throws(function () { + assert.throws(function() { types.Int64.serialize(so, {}); }); }); - it('Parse "0123456789ABCDEF"', function () { - var so = new SerializedObject("0123456789ABCDEF"); + it('Parse "0123456789ABCDEF"', function() { + var so = new SerializedObject('0123456789ABCDEF'); var num = types.Int64.parse(so); assert.strictEqual(num.toString(), '0x123456789abcdef'); }); }); describe('Hash128', function() { - it('Serialize 0', function () { + it('Serialize 0', function() { var so = new SerializedObject(); types.Hash128.serialize(so, '00000000000000000000000000000000'); assert.strictEqual(so.to_hex(), '00000000000000000000000000000000'); }); - it('Serialize 102030405060708090A0B0C0D0E0F000', function () { + it('Serialize 102030405060708090A0B0C0D0E0F000', function() { var so = new SerializedObject(); types.Hash128.serialize(so, '102030405060708090A0B0C0D0E0F000'); assert.strictEqual(so.to_hex(), '102030405060708090A0B0C0D0E0F000'); }); - it('Serialize HASH128_MAX', function () { + it('Serialize HASH128_MAX', function() { var so = new SerializedObject(); types.Hash128.serialize(so, 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'); assert.strictEqual(so.to_hex(), 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'); }); - it('Parse 0', function () { + it('Parse 0', function() { var val = '00000000000000000000000000000000'; var so = new SerializedObject(val); var num = types.Hash128.parse(so); assert.strictEqual(num.to_hex(), val); }); - it('Parse 1', function () { + it('Parse 1', function() { var val = '00000000000000000000000000000001'; var so = new SerializedObject(val); var num = types.Hash128.parse(so); assert.strictEqual(num.to_hex(), val); }); - it('Parse HASH128_MAX', function () { + it('Parse HASH128_MAX', function() { var val = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'; var so = new SerializedObject(val); var num = types.Hash128.parse(so); @@ -384,7 +390,7 @@ describe('Serialized types', function() { }); describe('Hash160', function() { - it('Serialize 0', function () { + it('Serialize 0', function() { var hex = '0000000000000000000000000000000000000000'; var base58 = 'rrrrrrrrrrrrrrrrrrrrrhoLvTp'; var so = new SerializedObject(); @@ -395,7 +401,7 @@ describe('Serialized types', function() { types.Hash160.serialize(so, hex); assert.strictEqual(so.to_hex(), hex); }); - it('Serialize 1', function () { + it('Serialize 1', function() { var hex = '0000000000000000000000000000000000000001'; var base58 = 'rrrrrrrrrrrrrrrrrrrrBZbvji'; var so = new SerializedObject(); @@ -406,7 +412,7 @@ describe('Serialized types', function() { types.Hash160.serialize(so, hex); assert.strictEqual(so.to_hex(), hex); }); - it('Serialize FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', function () { + it('Serialize FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', function() { var hex = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'; var base58 = 'rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi'; var so = new SerializedObject(); @@ -417,25 +423,25 @@ describe('Serialized types', function() { types.Hash160.serialize(so, hex); assert.strictEqual(so.to_hex(), hex); }); - it('Parse 0', function () { + it('Parse 0', function() { var val = '0000000000000000000000000000000000000000'; var so = new SerializedObject(val); var num = types.Hash160.parse(so); assert.strictEqual(num.to_hex(), val); }); - it('Parse 1', function () { + it('Parse 1', function() { var val = '0000000000000000000000000000000000000001'; var so = new SerializedObject(val); var num = types.Hash160.parse(so); assert.strictEqual(num.to_hex(), val); }); - it('Parse HASH160_MAX', function () { + it('Parse HASH160_MAX', function() { var val = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'; var so = new SerializedObject(val); var num = types.Hash160.parse(so); assert.strictEqual(num.to_hex(), val); }); - it('Parse 0 as JSON', function () { + it('Parse 0 as JSON', function() { // Hash160 should be returned as hex in JSON, unlike // addresses. var val = '0000000000000000000000000000000000000000'; @@ -446,34 +452,34 @@ describe('Serialized types', function() { }); describe('Hash256', function() { - it('Serialize 0', function () { + it('Serialize 0', function() { var so = new SerializedObject(); types.Hash256.serialize(so, '0000000000000000000000000000000000000000000000000000000000000000'); assert.strictEqual(so.to_hex(), '0000000000000000000000000000000000000000000000000000000000000000'); }); - it('Serialize 1', function () { + it('Serialize 1', function() { var so = new SerializedObject(); types.Hash256.serialize(so, '0000000000000000000000000000000000000000000000000000000000000001'); assert.strictEqual(so.to_hex(), '0000000000000000000000000000000000000000000000000000000000000001'); }); - it('Serialize HASH256_MAX', function () { + it('Serialize HASH256_MAX', function() { var so = new SerializedObject(); types.Hash256.serialize(so, 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'); assert.strictEqual(so.to_hex(), 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'); }); - it('Parse 0', function () { + it('Parse 0', function() { var val = '0000000000000000000000000000000000000000000000000000000000000000'; var so = new SerializedObject(val); var num = types.Hash256.parse(so); assert.strictEqual(num.to_hex(), val); }); - it('Parse 1', function () { + it('Parse 1', function() { var val = '0000000000000000000000000000000000000000000000000000000000000000'; var so = new SerializedObject(val); var num = types.Hash256.parse(so); assert.strictEqual(num.to_hex(), val); }); - it('Parse HASH256_MAX', function () { + it('Parse HASH256_MAX', function() { var val = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'; var so = new SerializedObject(val); var num = types.Hash256.parse(so); @@ -481,168 +487,181 @@ describe('Serialized types', function() { }); }); + describe('Quality', function() { + it('Serialize 1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { + var so = new SerializedObject(); + types.Quality.serialize(so, '1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); + assert.strictEqual(so.to_hex(), '55038D7EA4C68000'); + }); + it('Serialize 87654321.12345678/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { + var so = new SerializedObject(); + types.Quality.serialize(so, '87654321.12345678/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); + assert.strictEqual(so.to_hex(), '5C1F241D335BF24E'); + }); + }); + describe('Amount', function() { - it('Serialize 0 XRP', function () { + it('Serialize 0 XRP', function() { var so = new SerializedObject(); types.Amount.serialize(so, '0'); assert.strictEqual(so.to_hex(), '4000000000000000'); }); - it('Serialize 1 XRP', function () { + it('Serialize 1 XRP', function() { var so = new SerializedObject(); types.Amount.serialize(so, '1'); assert.strictEqual(so.to_hex(), '4000000000000001'); }); - it('Serialize -1 XRP', function () { + it('Serialize -1 XRP', function() { var so = new SerializedObject(); types.Amount.serialize(so, '-1'); assert.strictEqual(so.to_hex(), '0000000000000001'); }); - it('Serialize 213 XRP', function () { + it('Serialize 213 XRP', function() { var so = new SerializedObject(); types.Amount.serialize(so, '213'); assert.strictEqual(so.to_hex(), '40000000000000D5'); }); - it('Serialize 270544960 XRP', function () { + it('Serialize 270544960 XRP', function() { var so = new SerializedObject(); types.Amount.serialize(so, '270544960'); assert.strictEqual(so.to_hex(), '4000000010203040'); }); - it('Serialize 1161981756646125568 XRP', function () { + it('Serialize 1161981756646125568 XRP', function() { var so = new SerializedObject(); assert.throws(function() { var amt = Amount.from_json('1161981756646125696'); types.Amount.serialize(so, amt); }); }); - it('Serialize 1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () { + it('Serialize 1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { var so = new SerializedObject(); types.Amount.serialize(so, '1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); assert.strictEqual(so.to_hex(), 'D4838D7EA4C680000000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8'); }); - it('Serialize 87654321.12345678/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () { + it('Serialize 87654321.12345678/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { var so = new SerializedObject(); types.Amount.serialize(so, '87654321.12345678/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); assert.strictEqual(so.to_hex(), 'D65F241D335BF24E0000000000000000000000004555520000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8'); }); - it('Serialize -1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () { + it('Serialize -1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { var so = new SerializedObject(); types.Amount.serialize(so, '-1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); assert.strictEqual(so.to_hex(), '94838D7EA4C680000000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8'); }); - it('Serialize 15/XRP/rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo', function () { + it('Serialize 15/XRP/rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo', function() { // This actually appears in the ledger, so we need to be able to serialize // Transaction #A2AD66C93C7B7277CD5AEB718A4E82D88C7099129948BC66A394EE38B34657A9 var so = new SerializedObject(); types.Amount.serialize(so, { - "value":"1000", - "currency":"XRP", - "issuer":"rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo" + value: '1000', + currency: 'XRP', + issuer: 'rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo' }); assert.strictEqual(so.to_hex(), 'D5438D7EA4C680000000000000000000000000005852500000000000E4FE687C90257D3D2D694C8531CDEECBE84F3367'); }); // Test support for 20-byte hex raw currency codes - it('Serialize 15/015841551A748AD23FEFFFFFFFEA028000000000/1', function () { + it('Serialize 15/015841551A748AD23FEFFFFFFFEA028000000000/1', function() { var so = new SerializedObject(); types.Amount.serialize(so, { - "value":"1000", - "currency":"015841551A748AD23FEFFFFFFFEA028000000000", - "issuer":"rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo" + value: '1000', + currency: '015841551A748AD23FEFFFFFFFEA028000000000', + issuer: 'rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo' }); assert.strictEqual(so.to_hex(), 'D5438D7EA4C68000015841551A748AD23FEFFFFFFFEA028000000000E4FE687C90257D3D2D694C8531CDEECBE84F3367'); }); - it('Serialize max_value/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () { + it('Serialize max_value/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { var so = new SerializedObject(); - types.Amount.serialize(so, Amount.max_value+'/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); + types.Amount.serialize(so, Amount.max_value + '/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); assert.strictEqual(so.to_hex(), 'EC6386F26FC0FFFF0000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8'); }); - it('Parse 1 XRP', function () { + it('Parse 1 XRP', function() { var so = new SerializedObject('4000000000000001'); assert.strictEqual(types.Amount.parse(so).to_json(), '1'); }); - it('Parse -1 XRP', function () { + it('Parse -1 XRP', function() { var so = new SerializedObject('0000000000000001'); assert.strictEqual(types.Amount.parse(so).to_json(), '-1'); }); - it('Parse 213 XRP', function () { + it('Parse 213 XRP', function() { var so = new SerializedObject('40000000000000D5'); assert.strictEqual(types.Amount.parse(so).to_json(), '213'); }); - it('Parse 270544960 XRP', function () { + it('Parse 270544960 XRP', function() { var so = new SerializedObject('4000000010203040'); assert.strictEqual(types.Amount.parse(so).to_json(), '270544960'); }); - it('Parse 1161981756646125568 XRP', function () { + it('Parse 1161981756646125568 XRP', function() { assert.throws(function() { // hex(1161981756646125568) = 1020304050607000 var so = new SerializedObject('1020304050607000'); types.Amount.parse(so).to_json(); - }) + }); }); - it('Parse 1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () { + it('Parse 1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { var so = new SerializedObject('D4838D7EA4C680000000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8'); assert.strictEqual(types.Amount.parse(so).to_text_full(), '1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); }); - it('Parse 87654321.12345678/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () { + it('Parse 87654321.12345678/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { var so = new SerializedObject('D65F241D335BF24E0000000000000000000000004555520000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8'); assert.strictEqual(types.Amount.parse(so).to_text_full(), '87654321.12345678/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); }); - it('Parse -1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () { + it('Parse -1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { var so = new SerializedObject('94838D7EA4C680000000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8'); assert.strictEqual(types.Amount.parse(so).to_text_full(), '-1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); }); - it('Parse max_value/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () { + it('Parse max_value/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { var so = new SerializedObject('EC6386F26FC0FFFF0000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8'); - assert.strictEqual(types.Amount.parse(so).to_text_full(), Amount.max_value+'/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); + assert.strictEqual(types.Amount.parse(so).to_text_full(), Amount.max_value + '/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); }); }); describe('Account', function() { - it('Serialize 0', function () { + it('Serialize 0', function() { var hex = '0000000000000000000000000000000000000000'; var base58 = 'rrrrrrrrrrrrrrrrrrrrrhoLvTp'; var so = new SerializedObject(); types.Account.serialize(so, base58); - assert.strictEqual(so.to_hex(), "14"+hex); + assert.strictEqual(so.to_hex(), '14' + hex); so = new SerializedObject(); types.Account.serialize(so, hex); - assert.strictEqual(so.to_hex(), "14"+hex); + assert.strictEqual(so.to_hex(), '14' + hex); }); - it('Serialize 1', function () { + it('Serialize 1', function() { var hex = '0000000000000000000000000000000000000001'; var base58 = 'rrrrrrrrrrrrrrrrrrrrBZbvji'; var so = new SerializedObject(); types.Account.serialize(so, base58); - assert.strictEqual(so.to_hex(), "14"+hex); + assert.strictEqual(so.to_hex(), '14' + hex); so = new SerializedObject(); types.Account.serialize(so, hex); - assert.strictEqual(so.to_hex(), "14"+hex); + assert.strictEqual(so.to_hex(), '14' + hex); }); - it('Serialize FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', function () { + it('Serialize FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', function() { var hex = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'; var base58 = 'rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi'; var so = new SerializedObject(); types.Account.serialize(so, base58); - assert.strictEqual(so.to_hex(), "14"+hex); + assert.strictEqual(so.to_hex(), '14' + hex); so = new SerializedObject(); types.Account.serialize(so, hex); - assert.strictEqual(so.to_hex(), "14"+hex); + assert.strictEqual(so.to_hex(), '14' + hex); }); - it('Parse 0', function () { + it('Parse 0', function() { var val = '140000000000000000000000000000000000000000'; var so = new SerializedObject(val); var num = types.Account.parse(so); assert.strictEqual(num.to_json(), 'rrrrrrrrrrrrrrrrrrrrrhoLvTp'); }); - it('Parse 1', function () { + it('Parse 1', function() { var val = '140000000000000000000000000000000000000001'; var so = new SerializedObject(val); var num = types.Account.parse(so); assert.strictEqual(num.to_json(), 'rrrrrrrrrrrrrrrrrrrrBZbvji'); }); - it('Parse HASH160_MAX', function () { + it('Parse HASH160_MAX', function() { var val = '14FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'; var so = new SerializedObject(val); var num = types.Account.parse(so); @@ -651,44 +670,44 @@ describe('Serialized types', function() { }); describe('PathSet', function() { - it('Serialize single empty path [[]]', function () { + it('Serialize single empty path [[]]', function() { var so = new SerializedObject(); types.PathSet.serialize(so, [[]]); assert.strictEqual(so.to_hex(), '00'); }); - it('Serialize [[e],[e,e]]', function () { + it('Serialize [[e],[e,e]]', function() { var so = new SerializedObject(); - types.PathSet.serialize(so, [[ { - account: 123, - currency: 'USD', - issuer: 789 + types.PathSet.serialize(so, [[{ + account: 123, + currency: 'USD', + issuer: 789 }], [{ - account: 123, - currency: 'BTC', - issuer: 789 + account: 123, + currency: 'BTC', + issuer: 789 }, { - account: 987, - currency: 'EUR', - issuer: 321 + account: 987, + currency: 'EUR', + issuer: 321 }]]); - assert.strictEqual(so.to_hex(), '31000000000000000000000000000000000000007B00000000000000000000000055534400000000000000000000000000000000000000000000000315FF31000000000000000000000000000000000000007B000000000000000000000000425443000000000000000000000000000000000000000000000003153100000000000000000000000000000000000003DB0000000000000000000000004555520000000000000000000000000000000000000000000000014100'); //TODO: Check this independently + assert.strictEqual(so.to_hex(), '31000000000000000000000000000000000000007B00000000000000000000000055534400000000000000000000000000000000000000000000000315FF31000000000000000000000000000000000000007B000000000000000000000000425443000000000000000000000000000000000000000000000003153100000000000000000000000000000000000003DB0000000000000000000000004555520000000000000000000000000000000000000000000000014100'); // TODO: Check this independently }); - it('Serialize path through XRP', function () { + it('Serialize path through XRP', function() { var hex = '31000000000000000000000000000000000000007B00000000000000000000000055534400000000000000000000000000000000000000000000000315FF1000000000000000000000000000000000000000003100000000000000000000000000000000000003DB0000000000000000000000004555520000000000000000000000000000000000000000000000014100'; var json = [ [{ - account: "rrrrrrrrrrrrrrrrrrrrNxV3Xza", - currency: 'USD', - issuer: "rrrrrrrrrrrrrrrrrrrpYnYCNYf" + account: 'rrrrrrrrrrrrrrrrrrrrNxV3Xza', + currency: 'USD', + issuer: 'rrrrrrrrrrrrrrrrrrrpYnYCNYf' }], [{ - currency: "XRP" + currency: 'XRP' }, { - account: "rrrrrrrrrrrrrrrrrrrpvQsW3V3", - currency: 'EUR', - issuer: "rrrrrrrrrrrrrrrrrrrdHRtqg2" + account: 'rrrrrrrrrrrrrrrrrrrpvQsW3V3', + currency: 'EUR', + issuer: 'rrrrrrrrrrrrrrrrrrrdHRtqg2' }] ]; @@ -721,21 +740,21 @@ describe('Serialized types', function() { var parsed_path = SerializedObject.jsonify_structure(types.PathSet.parse(so)); assert.deepEqual(parsed_path, result_json); }); - it('Serialize path through XRP IOUs', function () { + it('Serialize path through XRP IOUs', function() { var hex = '31000000000000000000000000000000000000007B00000000000000000000000055534400000000000000000000000000000000000000000000000315FF1000000000000000000000000058525000000000003100000000000000000000000000000000000003DB0000000000000000000000004555520000000000000000000000000000000000000000000000014100'; var json = [ [{ - account: "rrrrrrrrrrrrrrrrrrrrNxV3Xza", - currency: 'USD', - issuer: "rrrrrrrrrrrrrrrrrrrpYnYCNYf" + account: 'rrrrrrrrrrrrrrrrrrrrNxV3Xza', + currency: 'USD', + issuer: 'rrrrrrrrrrrrrrrrrrrpYnYCNYf' }], [{ - currency: "XRP", + currency: 'XRP', non_native: true }, { - account: "rrrrrrrrrrrrrrrrrrrpvQsW3V3", - currency: 'EUR', - issuer: "rrrrrrrrrrrrrrrrrrrdHRtqg2" + account: 'rrrrrrrrrrrrrrrrrrrpvQsW3V3', + currency: 'EUR', + issuer: 'rrrrrrrrrrrrrrrrrrrdHRtqg2' }] ]; @@ -769,7 +788,7 @@ describe('Serialized types', function() { var parsed_path = SerializedObject.jsonify_structure(types.PathSet.parse(so)); assert.deepEqual(parsed_path, result_json); }); - it('Serialize path through XRP IOUs (realistic example)', function () { + it('Serialize path through XRP IOUs (realistic example)', function() { // Appears in the history // TX #0CBB429C456ED999CC691DFCC8E62E8C8C7E9522C2BEA967FED0D7E2A9B28D13 // Note that XRP IOUs are no longer allowed, so this functionality is @@ -778,51 +797,51 @@ describe('Serialized types', function() { var hex = '31585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C10000000000000000000000004254430000000000585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C131E4FE687C90257D3D2D694C8531CDEECBE84F33670000000000000000000000004254430000000000E4FE687C90257D3D2D694C8531CDEECBE84F3367310A20B3C85F482532A9578DBB3950B85CA06594D100000000000000000000000042544300000000000A20B3C85F482532A9578DBB3950B85CA06594D13000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D1FF31585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C10000000000000000000000004254430000000000585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C131E4FE687C90257D3D2D694C8531CDEECBE84F33670000000000000000000000004254430000000000E4FE687C90257D3D2D694C8531CDEECBE84F33673115036E2D3F5437A83E5AC3CAEE34FF2C21DEB618000000000000000000000000425443000000000015036E2D3F5437A83E5AC3CAEE34FF2C21DEB6183000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D1FF31585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C10000000000000000000000004254430000000000585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C13157180C769B66D942EE69E6DCC940CA48D82337AD000000000000000000000000425443000000000057180C769B66D942EE69E6DCC940CA48D82337AD1000000000000000000000000058525000000000003000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D100'; var json = [ [{ - "account": "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K", - "currency": "BTC", - "issuer": "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K" + account: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K', + currency: 'BTC', + issuer: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K' }, { - "account": "rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo", - "currency": "BTC", - "issuer": "rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo" + account: 'rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo', + currency: 'BTC', + issuer: 'rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo' }, { - "account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", - "currency": "BTC", - "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + account: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', + currency: 'BTC', + issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' }, { - "currency": "USD", - "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + currency: 'USD', + issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' }], [{ - "account": "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K", - "currency": "BTC", - "issuer": "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K" + account: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K', + currency: 'BTC', + issuer: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K' }, { - "account": "rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo", - "currency": "BTC", - "issuer": "rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo" + account: 'rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo', + currency: 'BTC', + issuer: 'rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo' }, { - "account": "rpvfJ4mR6QQAeogpXEKnuyGBx8mYCSnYZi", - "currency": "BTC", - "issuer": "rpvfJ4mR6QQAeogpXEKnuyGBx8mYCSnYZi" + account: 'rpvfJ4mR6QQAeogpXEKnuyGBx8mYCSnYZi', + currency: 'BTC', + issuer: 'rpvfJ4mR6QQAeogpXEKnuyGBx8mYCSnYZi' }, { - "currency": "USD", - "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + currency: 'USD', + issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' }], [{ - "account": "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K", - "currency": "BTC", - "issuer": "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K" + account: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K', + currency: 'BTC', + issuer: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K' }, { - "account": "r3AWbdp2jQLXLywJypdoNwVSvr81xs3uhn", - "currency": "BTC", - "issuer": "r3AWbdp2jQLXLywJypdoNwVSvr81xs3uhn" + account: 'r3AWbdp2jQLXLywJypdoNwVSvr81xs3uhn', + currency: 'BTC', + issuer: 'r3AWbdp2jQLXLywJypdoNwVSvr81xs3uhn' }, { - "currency": "XRP", - "non_native": true + currency: 'XRP', + non_native: true }, { - "currency": "USD", - "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + currency: 'USD', + issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' }] ]; @@ -908,134 +927,197 @@ describe('Serialized types', function() { var parsed_path = SerializedObject.jsonify_structure(types.PathSet.parse(so)); assert.deepEqual(parsed_path, result_json); }); - it('Parse single empty path [[]]', function () { + it('Parse single empty path [[]]', function() { var so = new SerializedObject('00'); var parsed_path = SerializedObject.jsonify_structure(types.PathSet.parse(so)); assert.deepEqual(parsed_path, [[]]); }); - it('Parse [[e],[e,e]]', function () { + it('Parse [[e],[e,e]]', function() { var so = new SerializedObject('31000000000000000000000000000000000000007B00000000000000000000000055534400000000000000000000000000000000000000000000000315FF31000000000000000000000000000000000000007B000000000000000000000000425443000000000000000000000000000000000000000000000003153100000000000000000000000000000000000003DB0000000000000000000000004555520000000000000000000000000000000000000000000000014100'); var parsed_path = types.PathSet.parse(so); - var comp = [ [ { account: 'rrrrrrrrrrrrrrrrrrrrNxV3Xza', - currency: 'USD', - issuer: 'rrrrrrrrrrrrrrrrrrrpYnYCNYf', - type: 49, - type_hex: '0000000000000031' } ], - [ { account: 'rrrrrrrrrrrrrrrrrrrrNxV3Xza', - currency: 'BTC', - issuer: 'rrrrrrrrrrrrrrrrrrrpYnYCNYf', - type: 49, - type_hex: '0000000000000031' }, - { account: 'rrrrrrrrrrrrrrrrrrrpvQsW3V3', - currency: 'EUR', - issuer: 'rrrrrrrrrrrrrrrrrrrdHRtqg2', - type: 49, - type_hex: '0000000000000031' } ] ]; + var comp = [ + [ + { + account: 'rrrrrrrrrrrrrrrrrrrrNxV3Xza', + currency: 'USD', + issuer: 'rrrrrrrrrrrrrrrrrrrpYnYCNYf', + type: 49, + type_hex: '0000000000000031' + } + ], + [ + { + account: 'rrrrrrrrrrrrrrrrrrrrNxV3Xza', + currency: 'BTC', + issuer: 'rrrrrrrrrrrrrrrrrrrpYnYCNYf', + type: 49, + type_hex: '0000000000000031' + }, + { + account: 'rrrrrrrrrrrrrrrrrrrpvQsW3V3', + currency: 'EUR', + issuer: 'rrrrrrrrrrrrrrrrrrrdHRtqg2', + type: 49, + type_hex: '0000000000000031' + } + ] + ]; - assert.deepEqual(SerializedObject.jsonify_structure(parsed_path, ""), comp); + assert.deepEqual(SerializedObject.jsonify_structure(parsed_path, ''), comp); }); }); describe('Object', function() { it('Can parse objects with VL encoded Vector256', function() { var hex = '110064220000000058000360186E008422E06B72D5B275E29EE3BE9D87A370F424E0E7BF613C4659098214289D19799C892637306AAAF03805EDFCDF6C28B8011320081342A0AB45459A54D8E4FA1842339A102680216CF9A152BCE4F4CE467D8246'; - var so = new SerializedObject(hex); + var so = new SerializedObject(hex); var as_json = so.to_json(); var expected_json = { - "LedgerEntryType": "DirectoryNode", - "Owner": "rh6kN9s7spSb3vdv6H8ZGYzsddSLeEUGmc", - "Flags": 0, - "Indexes": [ - "081342A0AB45459A54D8E4FA1842339A102680216CF9A152BCE4F4CE467D8246" + LedgerEntryType: 'DirectoryNode', + Owner: 'rh6kN9s7spSb3vdv6H8ZGYzsddSLeEUGmc', + Flags: 0, + Indexes: [ + '081342A0AB45459A54D8E4FA1842339A102680216CF9A152BCE4F4CE467D8246' ], - "RootIndex": "000360186E008422E06B72D5B275E29EE3BE9D87A370F424E0E7BF613C465909" - } + RootIndex: '000360186E008422E06B72D5B275E29EE3BE9D87A370F424E0E7BF613C465909' + }; assert.deepEqual(as_json, expected_json); - assert.strictEqual(SerializedObject.from_json(expected_json).to_hex(), hex) + assert.strictEqual(SerializedObject.from_json(expected_json).to_hex(), hex); }); - it('Serialize empty object {}', function () { + it('Serialize empty object {}', function() { var so = new SerializedObject(); types.Object.serialize(so, {}); assert.strictEqual(so.to_hex(), 'E1'); }); - it('Parse empty object {}', function () { + it('Parse empty object {}', function() { var so = new SerializedObject('E1'); - var parsed_object = types.Object.parse(so) - assert.deepEqual(parsed_object, { }); + var parsed_object = types.Object.parse(so); + assert.deepEqual(parsed_object, {}); }); - it('Serialize simple object {TakerPays:"87654321.12345678/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", TakerGets:213, Fee:"789"}', function () { + it('Serialize simple object {TakerPays:"87654321.12345678/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", TakerGets:213, Fee:"789"}', function() { var so = new SerializedObject(); types.Object.serialize(so, { - TakerPays: '87654321.12345678/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', - TakerGets: '213', - Fee: 789 + TakerPays: '87654321.12345678/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + TakerGets: '213', + Fee: 789 }); assert.strictEqual(so.to_hex(), '64D65F241D335BF24E0000000000000000000000004555520000000000B5F762798A53D543A014CAF8B297CFF8F2F937E86540000000000000D5684000000000000315E1'); - //TODO: Check independently. + // TODO: Check independently. }); - it('Parse same object', function () { + it('Parse same object', function() { var so = new SerializedObject('64D65F241D335BF24E0000000000000000000000004555520000000000B5F762798A53D543A014CAF8B297CFF8F2F937E86540000000000000D5684000000000000315E1'); - var parsed_object=types.Object.parse(so); - var comp = { TakerPays: - { value: '87654321.12345678', - currency: 'EUR', - issuer: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh' }, - TakerGets: '213', - Fee: '789' }; - assert.deepEqual(SerializedObject.jsonify_structure(parsed_object, ""), comp); - //TODO: Check independently. + var parsed_object = types.Object.parse(so); + var comp = { + TakerPays: { + value: '87654321.12345678', + currency: 'EUR', + issuer: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh' + }, + TakerGets: '213', + Fee: '789' + }; + assert.deepEqual(SerializedObject.jsonify_structure(parsed_object, ''), comp); + // TODO: Check independently. }); - it('Serialize simple object {DestinationTag:123, QualityIn:456, QualityOut:789}', function () { + it('Serialize simple object {DestinationTag:123, QualityIn:456, QualityOut:789}', function() { var so = new SerializedObject(); - types.Object.serialize(so, {DestinationTag:123, QualityIn:456, QualityOut:789}); + types.Object.serialize(so, { + DestinationTag: 123, + QualityIn: 456, + QualityOut: 789 + }); assert.strictEqual(so.to_hex(), '2E0000007B2014000001C8201500000315E1'); - //TODO: Check independently. + // TODO: Check independently. }); - it('Parse simple object {DestinationTag:123, QualityIn:456, QualityOut:789}', function () { //2E0000007B22000001C82400000315E1 2E0000007B2002000001C8200200000315E1 + it('Parse simple object {DestinationTag:123, QualityIn:456, QualityOut:789}', function() {// 2E0000007B22000001C82400000315E1 2E0000007B2002000001C8200200000315E1 var so = new SerializedObject('2E0000007B2014000001C8201500000315E1'); - var parsed_object=types.Object.parse(so); - assert.deepEqual(parsed_object, { DestinationTag:123, QualityIn:456, QualityOut:789 }); - //TODO: Check independently. + var parsed_object = types.Object.parse(so); + assert.deepEqual(parsed_object, { + DestinationTag: 123, + QualityIn: 456, + QualityOut: 789 + }); + // TODO: Check independently. }); }); describe('Array', function() { - it('Serialize empty array []', function () { + it('Serialize empty array []', function() { var so = new SerializedObject(); types.Array.serialize(so, []); assert.strictEqual(so.to_hex(), 'F1'); }); - it('Parse empty array []', function () { + it('Parse empty array []', function() { var so = new SerializedObject('F1'); - var parsed_object=types.Array.parse(so); + var parsed_object = types.Array.parse(so); assert.deepEqual(parsed_object, []); }); - it('Serialize 3-length array [{TakerPays:123}); {TakerGets:456}, {Fee:789}]', function () { + it('Serialize 3-length array [{TakerPays:123}); {TakerGets:456}, {Fee:789}]', function() { var so = new SerializedObject(); - types.Array.serialize(so, [{TakerPays:123}, {TakerGets:456}, {Fee:789}]); - //TODO: Check this manually + types.Array.serialize(so, [ + { + TakerPays: 123 + }, + { + TakerGets: 456 + }, + { + Fee: 789 + } + ]); + // TODO: Check this manually assert.strictEqual(so.to_hex(), '64400000000000007B6540000000000001C8684000000000000315F1'); }); - it('Parse the same array', function () { + it('Parse the same array', function() { var so = new SerializedObject('64400000000000007B6540000000000001C8684000000000000315F1'); - var parsed_object=types.Array.parse(so); - var comp = [ { TakerPays: '123' }, { TakerGets: '456' }, { Fee: '789' } ]; - assert.deepEqual(SerializedObject.jsonify_structure(parsed_object, ""), comp); + var parsed_object = types.Array.parse(so); + var comp = [ + { + TakerPays: '123' + }, + { + TakerGets: '456' + }, + { + Fee: '789' + } + ]; + assert.deepEqual(SerializedObject.jsonify_structure(parsed_object, ''), comp); }); - it('Serialize 3-length array [{DestinationTag:123}); {QualityIn:456}, {Fee:789}]', function () { + it('Serialize 3-length array [{DestinationTag:123}); {QualityIn:456}, {Fee:789}]', function() { var so = new SerializedObject(); - types.Array.serialize(so, [{DestinationTag:123}, {QualityIn:456}, {Fee:789}]); - //TODO: Check this manually + types.Array.serialize(so, [ + { + DestinationTag: 123 + }, + { + QualityIn: 456 + }, + { + Fee: 789 + } + ]); + // TODO: Check this manually assert.strictEqual(so.to_hex(), '2E0000007B2014000001C8684000000000000315F1'); }); - it('Parse the same array 2', function () { + it('Parse the same array 2', function() { var so = new SerializedObject('2E0000007B2014000001C8684000000000000315F1'); var parsed_object = types.Array.parse(so); - var comp = [ { DestinationTag: 123 }, { QualityIn: 456 }, { Fee: '789' } ]; - //TODO: Is this correct? Return some things as integers, and others as objects? - assert.deepEqual( SerializedObject.jsonify_structure(parsed_object, ""), comp); + var comp = [ + { + DestinationTag: 123 + }, + { + QualityIn: 456 + }, + { + Fee: '789' + } + ]; + // TODO: Is this correct? Return some things as integers, and others as objects? + assert.deepEqual(SerializedObject.jsonify_structure(parsed_object, ''), comp); }); }); });