diff --git a/src/cpp/ripple/LedgerEntrySet.cpp b/src/cpp/ripple/LedgerEntrySet.cpp index e47ed099d9..09ecc4ab7e 100644 --- a/src/cpp/ripple/LedgerEntrySet.cpp +++ b/src/cpp/ripple/LedgerEntrySet.cpp @@ -1066,6 +1066,8 @@ STAmount LedgerEntrySet::rippleHolds(const uint160& uAccountID, const uint160& u return saBalance; } +// Returns the amount an account can spend without going into debt. +// // <-- saAmount: amount of uCurrencyID held by uAccountID. May be negative. STAmount LedgerEntrySet::accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID) { diff --git a/src/cpp/ripple/NetworkOPs.cpp b/src/cpp/ripple/NetworkOPs.cpp index fd3ff143a4..a3106768d0 100644 --- a/src/cpp/ripple/NetworkOPs.cpp +++ b/src/cpp/ripple/NetworkOPs.cpp @@ -1785,6 +1785,8 @@ void NetworkOPs::getBookPage(Ledger::pointer lpLedger, const uint160& uTakerPays // unsigned int iLeft = iLimit; + uint32 uTransferRate = lesActive.rippleTransferRate(uTakerGetsIssuerID); + while (!bDone) { if (bDirectAdvance) { bDirectAdvance = false; @@ -1814,33 +1816,62 @@ void NetworkOPs::getBookPage(Ledger::pointer lpLedger, const uint160& uTakerPays { SLE::pointer sleOffer = lesActive.entryCache(ltOFFER, uOfferIndex); const uint160 uOfferOwnerID = sleOffer->getFieldAccount(sfAccount).getAccountID(); + STAmount saTakerGets = sleOffer->getFieldAmount(sfTakerGets); + STAmount saTakerPays = sleOffer->getFieldAmount(sfTakerPays); STAmount saOwnerFunds; - boost::unordered_map::const_iterator umBalanceEntry = umBalance.find(uOfferOwnerID); - - if (umBalanceEntry == umBalance.end()) + if (uTakerGetsIssuerID == uOfferOwnerID) { - // Did not find balance in table. - STAmount saDefault(uTakerGetsCurrencyID, uTakerGetsIssuerID); - // cLog(lsINFO) << boost::str(boost::format("getBookPage: saDefault=%s") % saDefault.getFullText()); - - saOwnerFunds = lesActive.accountFunds(uOfferOwnerID, saDefault); - // cLog(lsINFO) << boost::str(boost::format("getBookPage: saOwnerFunds=%s (new)") % saOwnerFunds.getFullText()); + // If offer is selling issuer's own IOUs, it is fully funded. + saOwnerFunds = saTakerGets; } else { - saOwnerFunds = umBalanceEntry->second; - // cLog(lsINFO) << boost::str(boost::format("getBookPage: saOwnerFunds=%s (cached)") % saOwnerFunds.getFullText()); - } + boost::unordered_map::const_iterator umBalanceEntry = umBalance.find(uOfferOwnerID); - STAmount saTakerGets = sleOffer->getFieldAmount(sfTakerGets); - STAmount saTakerPays = sleOffer->getFieldAmount(sfTakerPays); + if (umBalanceEntry != umBalance.end()) + { + // Found in running balance table. + + saOwnerFunds = umBalanceEntry->second; + // cLog(lsINFO) << boost::str(boost::format("getBookPage: saOwnerFunds=%s (cached)") % saOwnerFunds.getFullText()); + } + else + { + // Did not find balance in table. + + saOwnerFunds = lesActive.accountHolds(uOfferOwnerID, uTakerGetsCurrencyID, uTakerGetsIssuerID); + // cLog(lsINFO) << boost::str(boost::format("getBookPage: saOwnerFunds=%s (new)") % saOwnerFunds.getFullText()); + if (saOwnerFunds.isNegative()) + { + // Treat negative funds as zero. + + saOwnerFunds.zero(); + } + } + } Json::Value jvOffer = sleOffer->getJson(0); STAmount saTakerGetsFunded; + STAmount saOwnerFundsLimit; + uint32 uOfferRate; - if (saOwnerFunds >= saTakerGets) + + if (uTransferRate != QUALITY_ONE // Have a tranfer fee. + && uTakerID != uTakerGetsIssuerID // Not taking offers of own IOUs. + && uTakerGetsIssuerID != uOfferOwnerID) { // Offer owner not issuing ownfunds + // Need to charge a transfer fee to offer owner. + uOfferRate = uTransferRate; + saOwnerFundsLimit = STAmount::divide(saOwnerFunds, STAmount(CURRENCY_ONE, ACCOUNT_ONE, uOfferRate, -9)); + } + else + { + uOfferRate = QUALITY_ONE; + saOwnerFundsLimit = saOwnerFunds; + } + + if (saOwnerFundsLimit >= saTakerGets) { // Sufficient funds no shenanigans. saTakerGetsFunded = saTakerGets; @@ -1855,17 +1886,21 @@ void NetworkOPs::getBookPage(Ledger::pointer lpLedger, const uint160& uTakerPays // cLog(lsINFO) << boost::str(boost::format("getBookPage: multiply=%s") % STAmount::multiply(saTakerGetsFunded, saDirRate, saTakerPays).getFullText()); STAmount saTakerPaysFunded; - saTakerGetsFunded = saOwnerFunds; + saTakerGetsFunded = saOwnerFundsLimit; saTakerPaysFunded = std::min(saTakerPays, STAmount::multiply(saTakerGetsFunded, saDirRate, saTakerPays)); // Only provide, if not fully funded. jvOffer["taker_gets_funded"] = saTakerGetsFunded.getJson(0); jvOffer["taker_pays_funded"] = saTakerPaysFunded.getJson(0); + } + STAmount saOwnerPays = QUALITY_ONE == uOfferRate + ? saTakerGetsFunded + : std::min(saOwnerFunds, STAmount::multiply(saTakerGetsFunded, STAmount(CURRENCY_ONE, ACCOUNT_ONE, uOfferRate, -9))); - STAmount saOwnerBalance = saOwnerFunds-saTakerGetsFunded; + STAmount saOwnerBalance = saOwnerFunds-saOwnerPays; - umBalance[uOfferOwnerID] = saOwnerBalance; + umBalance[uOfferOwnerID] = saOwnerBalance; if (!saOwnerFunds.isZero() || uOfferOwnerID == uTakerID) { diff --git a/src/cpp/ripple/OfferCreateTransactor.cpp b/src/cpp/ripple/OfferCreateTransactor.cpp index d2acfe9250..e4b59be8c5 100644 --- a/src/cpp/ripple/OfferCreateTransactor.cpp +++ b/src/cpp/ripple/OfferCreateTransactor.cpp @@ -321,6 +321,7 @@ TER OfferCreateTransactor::doApply() cLog(lsWARNING) << "OfferCreate> " << mTxn.getJson(0); const uint32 uTxFlags = mTxn.getFlags(); const bool bPassive = isSetBit(uTxFlags, tfPassive); + const bool bMarket = isSetBit(uTxFlags, tfMarket); STAmount saTakerPays = mTxn.getFieldAmount(sfTakerPays); STAmount saTakerGets = mTxn.getFieldAmount(sfTakerGets); @@ -460,6 +461,7 @@ TER OfferCreateTransactor::doApply() if (tesSUCCESS != terResult || !saTakerPays // Wants nothing more. || !saTakerGets // Offering nothing more. + || bMarket // Do not persist. || !mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).isPositive() // Not funded. || bUnfunded) // Consider unfunded. { diff --git a/src/cpp/ripple/TransactionFormats.h b/src/cpp/ripple/TransactionFormats.h index 7e69ce7416..a1c9161bb2 100644 --- a/src/cpp/ripple/TransactionFormats.h +++ b/src/cpp/ripple/TransactionFormats.h @@ -68,7 +68,8 @@ const uint32 tfAccountSetMask = ~(tfRequireDestTag|tfOptionalDestTag|tfRequireA // OfferCreate flags: const uint32 tfPassive = 0x00010000; -const uint32 tfOfferCreateMask = ~(tfPassive); +const uint32 tfMarket = 0x00020000; +const uint32 tfOfferCreateMask = ~(tfPassive|tfMarket); // Payment flags: const uint32 tfNoRippleDirect = 0x00010000; diff --git a/src/js/remote.js b/src/js/remote.js index 7eca87f002..c241a8b33e 100644 --- a/src/js/remote.js +++ b/src/js/remote.js @@ -876,6 +876,15 @@ Remote.prototype.request_transaction_entry = function (hash) { .tx_hash(hash); }; +// DEPRECATED: use request_transaction_entry +Remote.prototype.request_tx = function (hash) { + var request = new Request(this, 'tx'); + + request.message.transaction = hash; + + return request; +}; + Remote.prototype.request_account_info = function (accountID) { var request = new Request(this, 'account_info'); diff --git a/src/js/uint.js b/src/js/uint.js index d95ccb51fa..457e3e9590 100644 --- a/src/js/uint.js +++ b/src/js/uint.js @@ -106,14 +106,14 @@ UInt.prototype.parse_generic = function (j) { case undefined: case "0": case this.constructor.STR_ZERO: - case this.constructor.ADDRESS_ZERO: + case this.constructor.ACCOUNT_ZERO: case this.constructor.HEX_ZERO: this._value = nbi(); break; case "1": case this.constructor.STR_ONE: - case this.constructor.ADDRESS_ONE: + case this.constructor.ACCOUNT_ONE: case this.constructor.HEX_ONE: this._value = new BigInteger([1]); diff --git a/src/js/uint160.js b/src/js/uint160.js index 12c7ff04c6..66c980ec0e 100644 --- a/src/js/uint160.js +++ b/src/js/uint160.js @@ -24,8 +24,8 @@ UInt160.width = 20; UInt160.prototype = extend({}, UInt.prototype); UInt160.prototype.constructor = UInt160; -var ADDRESS_ZERO = UInt160.ADDRESS_ZERO = "rrrrrrrrrrrrrrrrrrrrrhoLvTp"; -var ADDRESS_ONE = UInt160.ADDRESS_ONE = "rrrrrrrrrrrrrrrrrrrrBZbvji"; +var ACCOUNT_ZERO = UInt160.ACCOUNT_ZERO = "rrrrrrrrrrrrrrrrrrrrrhoLvTp"; +var ACCOUNT_ONE = UInt160.ACCOUNT_ONE = "rrrrrrrrrrrrrrrrrrrrBZbvji"; var HEX_ZERO = UInt160.HEX_ZERO = "0000000000000000000000000000000000000000"; var HEX_ONE = UInt160.HEX_ONE = "0000000000000000000000000000000000000001"; var STR_ZERO = UInt160.STR_ZERO = utils.hexToString(HEX_ZERO); diff --git a/src/js/uint256.js b/src/js/uint256.js index ab5b6f70b6..42b01504a4 100644 --- a/src/js/uint256.js +++ b/src/js/uint256.js @@ -24,9 +24,6 @@ UInt256.width = 32; UInt256.prototype = extend({}, UInt.prototype); UInt256.prototype.constructor = UInt256; -// XXX Generate these constants (or remove them) -var ADDRESS_ZERO = UInt256.ADDRESS_ZERO = "XXX"; -var ADDRESS_ONE = UInt256.ADDRESS_ONE = "XXX"; var HEX_ZERO = UInt256.HEX_ZERO = "00000000000000000000000000000000" + "00000000000000000000000000000000"; var HEX_ONE = UInt256.HEX_ONE = "00000000000000000000000000000000" + diff --git a/test/amount-test.js b/test/amount-test.js index 31fa7ba085..6a62aff24f 100644 --- a/test/amount-test.js +++ b/test/amount-test.js @@ -17,16 +17,16 @@ buster.testCase("Amount", { buster.assert.equals(nbi(), UInt160.from_generic("0")._value); }, "Parse 0 export" : function () { - buster.assert.equals(UInt160.ADDRESS_ZERO, UInt160.from_generic("0").to_json()); + buster.assert.equals(UInt160.ACCOUNT_ZERO, UInt160.from_generic("0").to_json()); }, "Parse 1" : function () { buster.assert.equals(new BigInteger([1]), UInt160.from_generic("1")._value); }, "Parse rrrrrrrrrrrrrrrrrrrrrhoLvTp export" : function () { - buster.assert.equals(UInt160.ADDRESS_ZERO, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrrhoLvTp").to_json()); + buster.assert.equals(UInt160.ACCOUNT_ZERO, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrrhoLvTp").to_json()); }, "Parse rrrrrrrrrrrrrrrrrrrrBZbvji export" : function () { - buster.assert.equals(UInt160.ADDRESS_ONE, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrBZbvji").to_json()); + buster.assert.equals(UInt160.ACCOUNT_ONE, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrBZbvji").to_json()); }, "Parse mtgox export" : function () { buster.assert.equals(config.accounts["mtgox"].account, UInt160.from_json("mtgox").to_json());