diff --git a/src/cpp/ripple/LedgerEntrySet.cpp b/src/cpp/ripple/LedgerEntrySet.cpp index 23ee1bc7f..a4f131296 100644 --- a/src/cpp/ripple/LedgerEntrySet.cpp +++ b/src/cpp/ripple/LedgerEntrySet.cpp @@ -1023,25 +1023,51 @@ uint32 LedgerEntrySet::rippleQualityIn(const uint160& uToAccountID, const uint16 } // Return how much of uIssuerID's uCurrencyID IOUs that uAccountID holds. May be negative. -// <-- IOU's uAccountID has of uIssuerID -STAmount LedgerEntrySet::rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID) +// <-- IOU's uAccountID has of uIssuerID. +STAmount LedgerEntrySet::rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID, bool bAvail) { STAmount saBalance; SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uAccountID, uIssuerID, uCurrencyID)); - if (sleRippleState) + if (!sleRippleState) { - saBalance = sleRippleState->getFieldAmount(sfBalance); - - if (uAccountID > uIssuerID) + saBalance.zero(uCurrencyID, uIssuerID); + } + else if (uAccountID > uIssuerID) + { + if (bAvail) + { + saBalance = sleRippleState->getFieldAmount(sfLowLimit); + saBalance -= sleRippleState->getFieldAmount(sfBalance); + } + else + { + saBalance = sleRippleState->getFieldAmount(sfBalance); saBalance.negate(); // Put balance in uAccountID terms. + } + + saBalance.setIssuer(uIssuerID); + } + else + { + if (bAvail) + { + saBalance = sleRippleState->getFieldAmount(sfHighLimit); + saBalance += sleRippleState->getFieldAmount(sfBalance); + } + else + { + saBalance = sleRippleState->getFieldAmount(sfBalance); + } + + saBalance.setIssuer(uIssuerID); } return saBalance; } // <-- saAmount: amount of uCurrencyID held by uAccountID. May be negative. -STAmount LedgerEntrySet::accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID) +STAmount LedgerEntrySet::accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID, bool bAvail) { STAmount saAmount; @@ -1053,12 +1079,13 @@ STAmount LedgerEntrySet::accountHolds(const uint160& uAccountID, const uint160& } else { - saAmount = rippleHolds(uAccountID, uCurrencyID, uIssuerID); + saAmount = rippleHolds(uAccountID, uCurrencyID, uIssuerID, bAvail); } - cLog(lsINFO) << boost::str(boost::format("accountHolds: uAccountID=%s saAmount=%s") + cLog(lsINFO) << boost::str(boost::format("accountHolds: uAccountID=%s saAmount=%s bAvail=%d") % RippleAddress::createHumanAccountID(uAccountID) - % saAmount.getFullText()); + % saAmount.getFullText() + % bAvail); return saAmount; } @@ -1068,7 +1095,7 @@ STAmount LedgerEntrySet::accountHolds(const uint160& uAccountID, const uint160& // --> saDefault/currency/issuer // <-- saFunds: Funds available. May be negative. // If the issuer is the same as uAccountID, funds are unlimited, use result is saDefault. -STAmount LedgerEntrySet::accountFunds(const uint160& uAccountID, const STAmount& saDefault) +STAmount LedgerEntrySet::accountFunds(const uint160& uAccountID, const STAmount& saDefault, bool bAvail) { STAmount saFunds; @@ -1082,12 +1109,13 @@ STAmount LedgerEntrySet::accountFunds(const uint160& uAccountID, const STAmount& } else { - saFunds = accountHolds(uAccountID, saDefault.getCurrency(), saDefault.getIssuer()); + saFunds = accountHolds(uAccountID, saDefault.getCurrency(), saDefault.getIssuer(), bAvail); - cLog(lsINFO) << boost::str(boost::format("accountFunds: uAccountID=%s saDefault=%s saFunds=%s") + cLog(lsINFO) << boost::str(boost::format("accountFunds: uAccountID=%s saDefault=%s saFunds=%s bAvail=%d") % RippleAddress::createHumanAccountID(uAccountID) % saDefault.getFullText() - % saFunds.getFullText()); + % saFunds.getFullText() + % bAvail); } return saFunds; diff --git a/src/cpp/ripple/LedgerEntrySet.h b/src/cpp/ripple/LedgerEntrySet.h index 6015f9489..52958df73 100644 --- a/src/cpp/ripple/LedgerEntrySet.h +++ b/src/cpp/ripple/LedgerEntrySet.h @@ -119,14 +119,14 @@ public: uint32 rippleQualityOut(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) { return rippleQualityIn(uToAccountID, uFromAccountID, uCurrencyID, sfLowQualityOut, sfHighQualityOut); } - STAmount rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); + STAmount rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID, bool bAvail=false); STAmount rippleTransferFee(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount); void rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer=true); STAmount rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); - STAmount accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); + STAmount accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID, bool bAvail=false); + STAmount accountFunds(const uint160& uAccountID, const STAmount& saDefault, bool bAvail=false); void accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); - STAmount accountFunds(const uint160& uAccountID, const STAmount& saDefault); Json::Value getJson(int) const; void calcRawMeta(Serializer&, TER result, uint32 index); diff --git a/src/cpp/ripple/OfferCreateTransactor.cpp b/src/cpp/ripple/OfferCreateTransactor.cpp index 55a3d4a8b..e612c502b 100644 --- a/src/cpp/ripple/OfferCreateTransactor.cpp +++ b/src/cpp/ripple/OfferCreateTransactor.cpp @@ -113,8 +113,8 @@ TER OfferCreateTransactor::takeOffers( Log(lsINFO) << "takeOffers: saOfferPays=" << saOfferPays.getFullText(); - STAmount saOfferFunds = mEngine->getNodes().accountFunds(uOfferOwnerID, saOfferPays); - STAmount saTakerFunds = mEngine->getNodes().accountFunds(uTakerAccountID, saTakerPays); + STAmount saOfferFunds = mEngine->getNodes().accountFunds(uOfferOwnerID, saOfferPays, true); + STAmount saTakerFunds = mEngine->getNodes().accountFunds(uTakerAccountID, saTakerPays, true); SLE::pointer sleOfferAccount; // Owner of offer. if (!saOfferFunds.isPositive()) @@ -317,7 +317,7 @@ TER OfferCreateTransactor::doApply() terResult = temBAD_ISSUER; } - else if (!mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).isPositive()) + else if (!mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets, true).isPositive()) { Log(lsWARNING) << "doOfferCreate: delay: Offers must be at least partially funded."; @@ -348,7 +348,6 @@ TER OfferCreateTransactor::doApply() % saTakerPays.getFullText()); // Take using the parameters of the offer. -#if 1 Log(lsWARNING) << "doOfferCreate: takeOffers: BEFORE saTakerGets=" << saTakerGets.getFullText(); terResult = takeOffers( bPassive, @@ -360,9 +359,7 @@ TER OfferCreateTransactor::doApply() saOfferPaid, // How much was spent. saOfferGot // How much was got. ); -#else - terResult = tesSUCCESS; -#endif + Log(lsWARNING) << "doOfferCreate: takeOffers=" << terResult; Log(lsWARNING) << "doOfferCreate: takeOffers: saOfferPaid=" << saOfferPaid.getFullText(); Log(lsWARNING) << "doOfferCreate: takeOffers: saOfferGot=" << saOfferGot.getFullText(); @@ -379,7 +376,7 @@ TER OfferCreateTransactor::doApply() Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText(); Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << saTakerGets.getFullText(); Log(lsWARNING) << "doOfferCreate: takeOffers: mTxnAccountID=" << RippleAddress::createHumanAccountID(mTxnAccountID); - Log(lsWARNING) << "doOfferCreate: takeOffers: FUNDS=" << mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).getFullText(); + Log(lsWARNING) << "doOfferCreate: takeOffers: FUNDS=" << mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets, true).getFullText(); // Log(lsWARNING) << "doOfferCreate: takeOffers: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID); // Log(lsWARNING) << "doOfferCreate: takeOffers: uGetsIssuerID=" << RippleAddress::createHumanAccountID(uGetsIssuerID); @@ -387,7 +384,7 @@ TER OfferCreateTransactor::doApply() if (tesSUCCESS == terResult && saTakerPays // Still wanting something. && saTakerGets // Still offering something. - && mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).isPositive()) // Still funded. + && mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets, true).isPositive()) // Still funded. { // We need to place the remainder of the offer into its order book. Log(lsINFO) << boost::str(boost::format("doOfferCreate: offer not fully consumed: saTakerPays=%s saTakerGets=%s") diff --git a/test/offer-test.js b/test/offer-test.js index 8612f6b18..42fe64f02 100644 --- a/test/offer-test.js +++ b/test/offer-test.js @@ -15,7 +15,7 @@ buster.testRunner.timeout = 5000; buster.testCase("Offer tests", { 'setUp' : testutils.build_setup(), - // 'setUp' : testutils.build_setup({ verbose: true }), + // 'setUp' : testutils.build_setup({ verbose: true, standalone: false }), 'tearDown' : testutils.build_teardown(), "offer create then cancel in one ledger" : @@ -451,6 +451,90 @@ buster.testCase("Offer tests", { }); }, + "//=>ripple currency conversion : offerer into debt" : + // alice in, carol out + function (done) { + var self = this; + var seq; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol"], callback); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "2000/EUR/carol", + "bob" : "100/USD/alice", + "carol" : "1000/EUR/bob" + }, + callback); + }, + function (callback) { + self.what = "Create offer to exchange."; + + self.remote.transaction() + .offer_create("bob", "50/USD/alice", "200/EUR/carol") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + + seq = m.tx_json.Sequence; + }) + .submit(); + }, +// function (callback) { +// self.what = "Alice converts USD to EUR via ripple."; +// +// self.remote.transaction() +// .payment("alice", "alice", "10/EUR/carol") +// .send_max("50/USD/alice") +// .on('proposed', function (m) { +// // console.log("proposed: %s", JSON.stringify(m)); +// +// callback(m.result !== 'tesSUCCESS'); +// }) +// .submit(); +// }, + function (callback) { + self.what = "Alice converts USD to EUR via offer."; + + self.remote.transaction() + .offer_create("alice", "200/EUR/carol", "50/USD/alice") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + + seq = m.tx_json.Sequence; + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "-50/USD/bob", "200/EUR/carol" ], + "bob" : [ "50/USD/alice", "-200/EUR/carol" ], + "carol" : [ "-200/EUR/alice", "200/EUR/bob" ], + }, + callback); + }, + function (callback) { + self.what = "Verify offer consumed."; + + testutils.verify_offer_not_found(self.remote, "bob", seq, callback); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + "ripple currency conversion : in parts" : function (done) { var self = this;