From d5dc715d9c2f752ee69597add82b9f06827af819 Mon Sep 17 00:00:00 2001 From: Scott Schurr Date: Wed, 5 Apr 2017 15:08:21 -0700 Subject: [PATCH] Unit test offer crossing with lsfRequireAuth (RIPD-1346) --- src/ripple/app/paths/impl/BookStep.cpp | 31 ++++++++ src/test/app/Offer_test.cpp | 99 +++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/src/ripple/app/paths/impl/BookStep.cpp b/src/ripple/app/paths/impl/BookStep.cpp index 6c6e853064..2eacadb6b7 100644 --- a/src/ripple/app/paths/impl/BookStep.cpp +++ b/src/ripple/app/paths/impl/BookStep.cpp @@ -504,6 +504,7 @@ BookStep::forEachOffer ( FlowOfferStream offers ( sb, afView, book_, sb.parentCloseTime (), counter, j_); + bool const flowCross = afView.rules().enabled(featureFlowCross); bool offerAttempted = false; boost::optional ofrQ; while (offers.step ()) @@ -521,6 +522,36 @@ BookStep::forEachOffer ( strandSrc_, strandDst_, offer, ofrQ, offers, offerAttempted)) continue; + // Make sure offer owner has authorization to own IOUs from issuer. + // An account can always own their own IOUs. + if (flowCross && (offer.owner() != offer.issueIn().account)) + { + auto const& issuerID = offer.issueIn().account; + auto const issuer = afView.read (keylet::account (issuerID)); + if (issuer && ((*issuer)[sfFlags] & lsfRequireAuth)) + { + // Issuer requires authorization. See if offer owner has that. + auto const& ownerID = offer.owner(); + auto const authFlag = + ownerID > issuerID ? lsfHighAuth : lsfLowAuth; + + auto const line = afView.read (keylet::line ( + ownerID, issuerID, offer.issueOut().currency)); + + if (!line || (((*line)[sfFlags] & authFlag) == 0)) + { + // Offer owner not authorized to hold IOU from issuer. + // Remove this offer even if no crossing occurs. + offers.permRmOffer (offer.key()); + if (!offerAttempted) + // Change quality only if no previous offers were tried. + ofrQ = boost::none; + // This continue causes offers.step() to delete the offer. + continue; + } + } + } + if (! static_cast(this)->checkQualityThreshold(offer)) break; diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index cd3df1cf17..d0d676d184 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -4033,6 +4033,102 @@ public: } } + void testRequireAuth (std::initializer_list fs) + { + // Only test FlowCross. Results are different with Taker. + if (std::find (fs.begin(), fs.end(), featureFlowCross) == fs.end()) + return; + + testcase ("lsfRequireAuth"); + // 1. alice creates an offer to acquire USD/gw, an asset for which + // she does not have a trust line. At some point in the future, + // gw adds lsfRequireAuth. Then, later, alice's offer is crossed. + // alice's offer is deleted, not consumed, since alice is not + // authorized to hold USD/gw. + // + // 2. alice tries to create an offer for USD/gw, now that gw has + // lsfRequireAuth set. This time the offer create fails because + // alice is not authorized to hold USD/gw. + // + // 3. Now gw creates a trust line authorizing alice to own USD/gw. + // At this point alice successfully creates and crosses an offer + // for USD/gw. + + using namespace jtx; + + Env env {*this, fs}; + auto const closeTime = + fix1449Time() + + 100 * env.closed()->info().closeTimeResolution; + env.close (closeTime); + + auto const gw = Account("gw"); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gwUSD = gw["USD"]; + auto const aliceUSD = alice["USD"]; + auto const bobUSD = bob["USD"]; + + env.fund (XRP(400000), gw, alice, bob); + env.close(); + + env (offer (alice, gwUSD(40), XRP(4000))); + env.close(); + + env.require (offers (alice, 1)); + env.require (balance (alice, gwUSD(none))); + env(fset (gw, asfRequireAuth)); + env.close(); + + env (trust (gw, bobUSD(100)), txflags (tfSetfAuth)); + env.close(); + env (trust (bob, gwUSD(100))); + env.close(); + + env (pay(gw, bob, gwUSD(50))); + env.close(); + env.require (balance (bob, gwUSD(50))); + + // gw now requires authorization and bob has gwUSD(50). Let's see if + // bob can cross alice's offer. bob's offer shouldn't cross and + // alice's unauthorized offer should be deleted. + env (offer (bob, XRP(4000), gwUSD(40))); + env.close(); + + env.require (offers (alice, 0)); + env.require (balance (alice, gwUSD(none))); + + env.require (offers (bob, 1)); + env.require (balance (bob, gwUSD(50))); + + // See if alice can create an offer without authorization. alice + // should not be able to create the offer and bob's offer should be + // untouched. + env (offer (alice, gwUSD(40), XRP(4000)), ter(tecNO_LINE)); + env.close(); + + env.require (offers (alice, 0)); + env.require (balance (alice, gwUSD(none))); + + env.require (offers (bob, 1)); + env.require (balance (bob, gwUSD(50))); + + // Finally, set up an authorized trust line for alice. Now alice's + // offer should succeed. Note that, since this is an offer rather + // than a payment, alice does not need to set a trust line limit. + env (trust (gw, aliceUSD(100)), txflags (tfSetfAuth)); + env.close(); + + env (offer (alice, gwUSD(40), XRP(4000))); + env.close(); + + env.require (offers (alice, 0)); + env.require (balance (alice, gwUSD(40))); + + env.require (offers (bob, 0)); + env.require (balance (bob, gwUSD(10))); + } + void testTickSize (std::initializer_list fs) { testcase ("Tick Size"); @@ -4203,7 +4299,8 @@ public: testTinyOffer (fs); testSelfPayXferFeeOffer (fs); testSelfPayUnlimitedFunds (fs); - testTickSize(fs); + testRequireAuth (fs); + testTickSize (fs); }; // The following test variants passed at one time in the past (and should // still pass) but are commented out to conserve test time.