Merge pull request #234 from ripple/sum-account-offers

Track order funded status based on cumulative account offers
This commit is contained in:
Geert Weening
2014-12-19 17:27:38 -08:00
2 changed files with 199 additions and 13 deletions

View File

@@ -43,8 +43,9 @@ function OrderBook(remote, getsC, getsI, paysC, paysI, key) {
this._shouldSubscribe = true;
this._listeners = 0;
this._offers = [ ];
this._ownerFunds = { };
this._offerCounts = { };
this._ownerFunds = { };
this._ownerOffers = { };
// We consider ourselves synchronized if we have a current
// copy of the offers, we are online and subscribed to updates.
@@ -180,6 +181,7 @@ OrderBook.prototype.unsubscribe = function() {
OrderBook.prototype.resetCache = function() {
this._ownerFunds = { };
this._ownerOffers = { };
this._offerCounts = { };
this._synchronized = false;
};
@@ -232,6 +234,8 @@ OrderBook.prototype.removeCachedFunds = function(account) {
/**
* Get offer count for offer owner
*
* @param {String} account address
*/
OrderBook.prototype.getOfferCount = function(account) {
@@ -241,6 +245,8 @@ OrderBook.prototype.getOfferCount = function(account) {
/**
* Increment offer count for offer owner
*
* @param {String} account address
*/
OrderBook.prototype.incrementOfferCount = function(account) {
@@ -252,6 +258,8 @@ OrderBook.prototype.incrementOfferCount = function(account) {
/**
* Decrement offer count for offer owner
*
* @param {String} account address
*/
OrderBook.prototype.decrementOfferCount = function(account) {
@@ -261,6 +269,56 @@ OrderBook.prototype.decrementOfferCount = function(account) {
return result;
};
/**
* Add offer amount to sum amount being offered by an account
*
* @param {String} account address
* @param {Object|String} offer amount
* @return sum
*/
OrderBook.prototype.addOwnerOffer = function(account, amount) {
assert(UInt160.is_valid(account), 'Account is invalid');
var previousAmount = this.getOwnerOfferSum(account);
var newAmount = previousAmount.add(Amount.from_json(amount));
this._ownerOffers[account] = newAmount;
return newAmount;
};
/**
* Subtract offer amount from sum amount being offered by an account
*
* @param {String} account address
* @param {Object|String} offer amount
* @return sum
*/
OrderBook.prototype.subtractOwnerOffer = function(account, amount) {
assert(UInt160.is_valid(account), 'Account is invalid');
this._ownerOffers[account].subtract(Amount.from_json(amount));
return this._ownerOffers[account];
};
/**
* Get sum amount for offers by an account
*
* @param {String} account address
* @return sum
*/
OrderBook.prototype.getOwnerOfferSum = function(account) {
assert(UInt160.is_valid(account), 'Account is invalid');
var amount = this._ownerOffers[account];
if (!amount) {
if (typeof amount === 'string') {
amount = Amount.from_json('0');
} else {
amount = Amount.from_json('0' + OrderBook.IOU_SUFFIX);
}
}
return amount;
};
/**
* Compute funded amount for a balance/transferRate
*
@@ -358,11 +416,20 @@ OrderBook.prototype.setFundedAmount = function(offer, fundedAmount) {
return offer;
}
// Sum of offer amounts by an account
var offerSum = this.getOwnerOfferSum(offer.Account);
if (offerSum.is_zero()) {
// If there are no cached offer amounts for the account, default to
// TakerGets of this offer
offerSum = Amount.from_json(offer.TakerGets);
}
offer.is_fully_funded = Amount.from_json(
this._currencyGets.is_native()
? fundedAmount
: fundedAmount + OrderBook.IOU_SUFFIX
).compareTo(Amount.from_json(offer.TakerGets)) >= 0;
).compareTo(offerSum) >= 0;
if (offer.is_fully_funded) {
offer.taker_gets_funded = Amount.from_json(offer.TakerGets).to_text();
@@ -376,12 +443,8 @@ OrderBook.prototype.setFundedAmount = function(offer, fundedAmount) {
? offer.TakerPays.value
: offer.TakerPays;
var takerGetsValue = (typeof offer.TakerGets === 'object')
? offer.TakerGets.value
: offer.TakerGets;
var takerPays = Amount.from_json(takerPaysValue + OrderBook.IOU_SUFFIX);
var takerGets = Amount.from_json(takerGetsValue + OrderBook.IOU_SUFFIX);
var takerGets = Amount.from_json(offerSum);
var fundedPays = Amount.from_json(fundedAmount + OrderBook.IOU_SUFFIX);
var rate = takerPays.divide(takerGets);
@@ -662,9 +725,16 @@ OrderBook.offerRewrite = function(offer) {
OrderBook.prototype.setOffers = function(offers) {
assert(Array.isArray(offers));
var l = offers.length;
var newOffers = [ ];
for (var i=0, l=offers.length; i<l; i++) {
for (var i=0; i<l; i++) {
var offer = offers[i];
// Add offer amount to sum for account
this.addOwnerOffer(offer.Account, offer.TakerGets);
}
for (var i=0; i<l; i++) {
var offer = OrderBook.offerRewrite(offers[i]);
var fundedAmount;
@@ -675,8 +745,8 @@ OrderBook.prototype.setOffers = function(offers) {
this.addCachedFunds(offer.Account, fundedAmount);
}
this.setFundedAmount(offer, fundedAmount);
this.incrementOfferCount(offer.Account);
this.setFundedAmount(offer, fundedAmount);
newOffers.push(offer);
}
@@ -888,6 +958,9 @@ OrderBook.prototype.insertOffer = function(node, fundedAmount) {
log.info('inserting offer', this._key, node.fields);
}
// Add offer amount to sum for account
this.addOwnerOffer(node.fields.Account, node.fields.TakerGets);
var nodeFields = OrderBook.offerRewrite(node.fields);
nodeFields.LedgerEntryType = node.entryType;
nodeFields.index = node.ledgerIndex;
@@ -904,7 +977,7 @@ OrderBook.prototype.insertOffer = function(node, fundedAmount) {
// XXX Should use Amount#from_quality
var price = Amount.from_json(
nodeFields.TakerPays
).ratio_human(node.fields.TakerGets, DATE_REF);
).ratio_human(nodeFields.TakerGets, DATE_REF);
for (var i=0, l=this._offers.length; i<l; i++) {
var offer = this._offers[i];
@@ -944,14 +1017,21 @@ OrderBook.prototype.modifyOffer = function(node, isDeletedNode) {
var offer = this._offers[i];
if (offer.index === node.ledgerIndex) {
if (isDeletedNode) {
// Multiple offers same account?
// Remove offer amount from sum for account
this.subtractOwnerOffer(offer.Account, offer.TakerGets);
this._offers.splice(i, 1);
this.emit('offer_removed', offer);
} else {
// TODO: This assumes no fields are deleted, which is
// probably a safe assumption, but should be checked.
var previousOffer = extend({}, offer);
var previousOffer = extend({ }, offer);
extend(offer, node.fieldsFinal);
// Remove offer amount from sum for account
this.subtractOwnerOffer(offer.Account, previousOffer.TakerGets);
// Add offer amount from sum for account
this.addOwnerOffer(offer.Account, offer.TakerGets);
this.emit('offer_changed', previousOffer, offer);
}
break;

View File

@@ -337,6 +337,7 @@ describe('OrderBook', function() {
});
var offer = {
Account: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp',
TakerGets: {
value: '100',
currency: 'BTC',
@@ -348,6 +349,7 @@ describe('OrderBook', function() {
book.setFundedAmount(offer, '100.1234');
var expected = {
Account: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp',
TakerGets: offer.TakerGets,
TakerPays: offer.TakerPays,
is_fully_funded: true,
@@ -367,6 +369,7 @@ describe('OrderBook', function() {
});
var offer = {
Account: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp',
TakerGets: {
value: '100',
currency: 'BTC',
@@ -378,6 +381,7 @@ describe('OrderBook', function() {
book.setFundedAmount(offer, '99');
var expected = {
Account: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp',
TakerGets: offer.TakerGets,
TakerPays: offer.TakerPays,
is_fully_funded: false,
@@ -397,6 +401,7 @@ describe('OrderBook', function() {
});
var offer = {
Account: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp',
TakerGets: '100',
TakerPays: {
value: '123.456',
@@ -408,6 +413,7 @@ describe('OrderBook', function() {
book.setFundedAmount(offer, '100.1');
var expected = {
Account: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp',
TakerGets: offer.TakerGets,
TakerPays: offer.TakerPays,
is_fully_funded: true,
@@ -427,6 +433,7 @@ describe('OrderBook', function() {
});
var offer = {
Account: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp',
TakerGets: '100',
TakerPays: {
value: '123.456',
@@ -438,6 +445,7 @@ describe('OrderBook', function() {
book.setFundedAmount(offer, '99');
var expected = {
Account: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp',
TakerGets: offer.TakerGets,
TakerPays: offer.TakerPays,
is_fully_funded: false,
@@ -457,6 +465,7 @@ describe('OrderBook', function() {
});
var offer = {
Account: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp',
TakerGets: {
value: '100',
currency: 'BTC',
@@ -468,6 +477,7 @@ describe('OrderBook', function() {
book.setFundedAmount(offer, '0');
assert.deepEqual(offer, {
Account: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp',
TakerGets: offer.TakerGets,
TakerPays: offer.TakerPays,
is_fully_funded: false,
@@ -1450,6 +1460,56 @@ describe('OrderBook', function() {
index: 'A437D85DF80D250F79308F2B613CF5391C7CF8EE9099BC4E553942651CD9FA86',
owner_funds: '0.950363009783092',
quality: '498.6116758238228'
},
{
Account: 'rwBG69mujDoD5yQfL3Sf7Yuh7rUNYdxe9Y',
BookDirectory: '6EAB7C172DEFA430DBFAD120FDC373B5F5AF8B191649EC985711B6D8C62EF414',
BookNode: '0000000000000000',
Expiration: 461498565,
Flags: 131072,
LedgerEntryType: 'Offer',
OwnerNode: '0000000000000144',
PreviousTxnID: 'C8296B9CCA6DC594C7CD271C5D8FD11FEE380021A07768B25935642CDB37048A',
PreviousTxnLgrSeq: 8342469,
Sequence: 29356,
TakerGets: {
currency: 'BTC',
issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
value: '0.5'
},
TakerPays: {
currency: 'USD',
issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
value: '99.72233516476456'
},
index: 'A437D85DF80D250F79308F2B613CF5391C7CF8EE9099BC4E553942651CD9FA86',
owner_funds: '0.950363009783092',
quality: '498.6116758238228'
},
{
Account: 'rwBG69mujDoD5yQfL3Sf7Yuh7rUNYdxe9Y',
BookDirectory: '6EAB7C172DEFA430DBFAD120FDC373B5F5AF8B191649EC985711B6D8C62EF414',
BookNode: '0000000000000000',
Expiration: 461498565,
Flags: 131078,
LedgerEntryType: 'Offer',
OwnerNode: '0000000000000144',
PreviousTxnID: 'C8296B9CCA6DC594C7CD271C5D8FD11FEE380021A07768B25935642CDB37048A',
PreviousTxnLgrSeq: 8342469,
Sequence: 29354,
TakerGets: {
currency: 'BTC',
issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
value: '0.5'
},
TakerPays: {
currency: 'USD',
issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
value: '99.72233516476456'
},
index: 'A437D85DF80D250F79308F2B613CF5391C7CF8EE9099BC4E553942651CD9FA86',
owner_funds: '0.950363009783092',
quality: '498.6116758238228'
}
]
};
@@ -1533,6 +1593,52 @@ describe('OrderBook', function() {
is_fully_funded: true,
taker_gets_funded: '0.2',
taker_pays_funded: '99.72233516476456'
},
{ Account: 'rwBG69mujDoD5yQfL3Sf7Yuh7rUNYdxe9Y',
BookDirectory: '6EAB7C172DEFA430DBFAD120FDC373B5F5AF8B191649EC985711B6D8C62EF414',
BookNode: '0000000000000000',
Expiration: 461498565,
Flags: 131072,
LedgerEntryType: 'Offer',
OwnerNode: '0000000000000144',
Sequence: 29356,
TakerGets: { currency: 'BTC',
issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
value: '0.5'
},
TakerPays: {
currency: 'USD',
issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
value: '99.72233516476456'
},
index: 'A437D85DF80D250F79308F2B613CF5391C7CF8EE9099BC4E553942651CD9FA86',
owner_funds: '0.950363009783092',
is_fully_funded: false,
taker_gets_funded: '0.9484660776278363',
taker_pays_funded: '94.58325208561269' },
{ Account: 'rwBG69mujDoD5yQfL3Sf7Yuh7rUNYdxe9Y',
BookDirectory: '6EAB7C172DEFA430DBFAD120FDC373B5F5AF8B191649EC985711B6D8C62EF414',
BookNode: '0000000000000000',
Expiration: 461498565,
Flags: 131078,
LedgerEntryType: 'Offer',
OwnerNode: '0000000000000144',
Sequence: 29354,
TakerGets: {
currency: 'BTC',
issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
value: '0.5'
},
TakerPays: {
currency: 'USD',
issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
value: '99.72233516476456'
},
index: 'A437D85DF80D250F79308F2B613CF5391C7CF8EE9099BC4E553942651CD9FA86',
owner_funds: '0.950363009783092',
is_fully_funded: false,
taker_gets_funded: '0.9484660776278363',
taker_pays_funded: '94.58325208561269'
}
]