Compare commits

...

4 Commits

Author SHA1 Message Date
Geert Weening
3ba5a18b91 Bump version to 0.12.5-rc1 2015-04-29 11:54:36 -07:00
Geert Weening
bdb3415855 Update release notes 2015-04-29 11:54:05 -07:00
Geert Weening
5ef5bdd9d9 Merge pull request #332 from geertweening/develop
Add offer autobridging
2015-04-29 11:47:05 -07:00
Bo Chen
c7bbce8371 [FEATURE] add offer autobridging 2015-04-29 11:28:14 -07:00
11 changed files with 2051 additions and 605 deletions

View File

@@ -1,3 +1,7 @@
##0.12.15
+ [Add offer autobridging](https://github.com/ripple/ripple-lib/commit/c7bbce83719c1e8c6a4fae5ca850e7515db1a4a5)
##0.12.4
+ [Improve entropy security](https://github.com/ripple/ripple-lib/commit/c7ba822320880037796f57876d1abb4e525648ed)

2
npm-shrinkwrap.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "ripple-lib",
"version": "0.12.4",
"version": "0.12.5-rc1",
"npm-shrinkwrap-version": "5.3.0",
"node-version": "v0.10.38",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "ripple-lib",
"version": "0.12.4",
"version": "0.12.5-rc1",
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
"files": [
"src/js/*",

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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',

View File

@@ -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);
});
});

View File

@@ -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();

File diff suppressed because it is too large Load Diff