diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj
index e979d82a33..53ea1e70fb 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj
+++ b/Builds/VisualStudio2015/RippleD.vcxproj
@@ -4229,6 +4229,10 @@
True
True
+
+ True
+ True
+
True
True
diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters
index 7a877baabd..fdd736f6a8 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters
@@ -5010,6 +5010,9 @@
test\app
+
+ test\app
+
test\app
diff --git a/src/ripple/app/main/Amendments.cpp b/src/ripple/app/main/Amendments.cpp
index e48133f3ba..d470eaf61d 100644
--- a/src/ripple/app/main/Amendments.cpp
+++ b/src/ripple/app/main/Amendments.cpp
@@ -51,7 +51,8 @@ supportedAmendments ()
{ "532651B4FD58DF8922A49BA101AB3E996E5BFBF95A913B3E392504863E63B164 TickSize" },
{ "E2E6F2866106419B88C50045ACE96368558C345566AC8F2BDF5A5B5587F0E6FA fix1368" },
{ "07D43DCE529B15A10827E5E04943B496762F9A88E3268269D69C44BE49E21104 Escrow" },
- { "86E83A7D2ECE3AD5FA87AB2195AE015C950469ABF0B72EAACED318F74886AE90 CryptoConditionsSuite" }
+ { "86E83A7D2ECE3AD5FA87AB2195AE015C950469ABF0B72EAACED318F74886AE90 CryptoConditionsSuite" },
+ { "48C4451D6C6A138453F056EB6793AFF4B5C57457A37BA63EF3541FF8CE873DC2 ToStrandV2"}
};
}
diff --git a/src/ripple/app/paths/impl/BookStep.cpp b/src/ripple/app/paths/impl/BookStep.cpp
index 634496b84d..ea71497945 100644
--- a/src/ripple/app/paths/impl/BookStep.cpp
+++ b/src/ripple/app/paths/impl/BookStep.cpp
@@ -27,6 +27,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -684,6 +685,13 @@ BookStep::check(StrandContext const& ctx) const
return temBAD_PATH_LOOP;
}
+ if (ctx.view.rules().enabled(featureToStrandV2) &&
+ ctx.seenDirectIssues[1].count(book_.out))
+ {
+ JLOG(j_.debug()) << "BookStep: loop detected: " << *this;
+ return temBAD_PATH_LOOP;
+ }
+
if (amendmentRIPD1443(ctx.view.info().parentCloseTime))
{
if (ctx.prevStep)
diff --git a/src/ripple/app/paths/impl/DirectStep.cpp b/src/ripple/app/paths/impl/DirectStep.cpp
index 9144050094..b96fa06e36 100644
--- a/src/ripple/app/paths/impl/DirectStep.cpp
+++ b/src/ripple/app/paths/impl/DirectStep.cpp
@@ -124,6 +124,12 @@ class DirectStepI : public StepImp
return src_;
}
+ boost::optional>
+ directStepAccts () const override
+ {
+ return std::make_pair(src_, dst_);
+ }
+
bool
redeems (ReadView const& sb, bool fwd) const override;
diff --git a/src/ripple/app/paths/impl/PaySteps.cpp b/src/ripple/app/paths/impl/PaySteps.cpp
index 28c2a59ab1..2d50568c06 100644
--- a/src/ripple/app/paths/impl/PaySteps.cpp
+++ b/src/ripple/app/paths/impl/PaySteps.cpp
@@ -21,6 +21,8 @@
#include
#include
#include
+#include
+#include
#include
#include
@@ -102,6 +104,8 @@ toStep (
JLOG (j.warn())
<< "Found offer/account payment step. Aborting payment strand.";
assert (0);
+ if (ctx.view.rules().enabled(featureToStrandV2))
+ return {temBAD_PATH, std::unique_ptr{}};
Throw (tefEXCEPTION, "Found offer/account payment step.");
}
@@ -132,7 +136,7 @@ toStep (
}
std::pair
-toStrand (
+toStrandV1 (
ReadView const& view,
AccountID const& src,
AccountID const& dst,
@@ -370,6 +374,308 @@ toStrand (
return {tesSUCCESS, std::move (result)};
}
+
+std::pair
+toStrandV2 (
+ ReadView const& view,
+ AccountID const& src,
+ AccountID const& dst,
+ Issue const& deliver,
+ boost::optional const& sendMaxIssue,
+ STPath const& path,
+ bool ownerPaysTransferFee,
+ beast::Journal j)
+{
+ if (isXRP(src) || isXRP(dst) ||
+ !isConsistent(deliver) || (sendMaxIssue && !isConsistent(*sendMaxIssue)))
+ return {temBAD_PATH, Strand{}};
+
+ for (auto const& pe : path)
+ {
+ auto const t = pe.getNodeType();
+
+ if ((t & ~STPathElement::typeAll) || !t)
+ return {temBAD_PATH, Strand{}};
+
+ bool const hasAccount = t & STPathElement::typeAccount;
+ bool const hasIssuer = t & STPathElement::typeIssuer;
+ bool const hasCurrency = t & STPathElement::typeCurrency;
+
+ if (hasAccount && (hasIssuer || hasCurrency))
+ return {temBAD_PATH, Strand{}};
+
+ if (hasIssuer && isXRP(pe.getIssuerID()))
+ return {temBAD_PATH, Strand{}};
+
+ if (hasAccount && isXRP(pe.getAccountID()))
+ return {temBAD_PATH, Strand{}};
+
+ if (hasCurrency && hasIssuer &&
+ isXRP(pe.getCurrency()) != isXRP(pe.getIssuerID()))
+ return {temBAD_PATH, Strand{}};
+ }
+
+ Issue curIssue = [&]
+ {
+ auto const& currency =
+ sendMaxIssue ? sendMaxIssue->currency : deliver.currency;
+ if (isXRP (currency))
+ return xrpIssue ();
+ return Issue{currency, src};
+ }();
+
+ auto hasCurrency = [](STPathElement const pe)
+ {
+ return pe.getNodeType () & STPathElement::typeCurrency;
+ };
+
+ std::vector normPath;
+ // reserve enough for the path, the implied source, destination,
+ // sendmax and deliver.
+ normPath.reserve(4 + path.size());
+ {
+ normPath.emplace_back(
+ STPathElement::typeAll, src, curIssue.currency, curIssue.account);
+
+ if (sendMaxIssue && sendMaxIssue->account != src &&
+ (path.empty() || !path[0].isAccount() ||
+ path[0].getAccountID() != sendMaxIssue->account))
+ {
+ normPath.emplace_back(sendMaxIssue->account, boost::none, boost::none);
+ }
+
+ for (auto const& i : path)
+ normPath.push_back(i);
+
+ auto const lastCurrency =
+ (*boost::find_if(boost::adaptors::reverse(normPath), hasCurrency))
+ .getCurrency();
+ if (lastCurrency != deliver.currency)
+ normPath.emplace_back(
+ boost::none, deliver.currency, deliver.account);
+
+ if (!((normPath.back().isAccount() &&
+ normPath.back().getAccountID() == deliver.account) ||
+ (dst == deliver.account)))
+ {
+ normPath.emplace_back(deliver.account, boost::none, boost::none);
+ }
+
+ if (!normPath.back().isAccount() ||
+ normPath.back().getAccountID() != dst)
+ {
+ normPath.emplace_back(dst, boost::none, boost::none);
+ }
+ }
+
+ auto const strandSrc = normPath.front().getAccountID ();
+ auto const strandDst = normPath.back().getAccountID ();
+
+ Strand result;
+ result.reserve (2 * normPath.size ());
+
+ /* A strand may not include the same account node more than once
+ in the same currency. In a direct step, an account will show up
+ at most twice: once as a src and once as a dst (hence the two element array).
+ The strandSrc and strandDst will only show up once each.
+ */
+ std::array, 2> seenDirectIssues;
+ // A strand may not include the same offer book more than once
+ boost::container::flat_set seenBookOuts;
+ seenDirectIssues[0].reserve (normPath.size());
+ seenDirectIssues[1].reserve (normPath.size());
+ seenBookOuts.reserve (normPath.size());
+ auto ctx = [&](bool isLast = false)
+ {
+ return StrandContext{view, result, strandSrc, strandDst, isLast,
+ ownerPaysTransferFee, seenDirectIssues, seenBookOuts, j};
+ };
+
+ for (std::size_t i = 0; i < normPath.size () - 1; ++i)
+ {
+ /* Iterate through the path elements considering them in pairs.
+ The first element of the pair is `cur` and the second element is
+ `next`. When an offer is one of the pairs, the step created will be for
+ `next`. This means when `cur` is an offer and `next` is an
+ account then no step is created, as a step has already been created for
+ that offer.
+ */
+ boost::optional impliedPE;
+ auto cur = &normPath[i];
+ auto const next = &normPath[i + 1];
+
+ if (cur->isAccount())
+ curIssue.account = cur->getAccountID ();
+ else if (cur->hasIssuer())
+ curIssue.account = cur->getIssuerID ();
+
+ if (cur->hasCurrency())
+ {
+ curIssue.currency = cur->getCurrency ();
+ if (isXRP(curIssue.currency))
+ curIssue.account = xrpAccount();
+ }
+
+ if (cur->isAccount() && next->isAccount())
+ {
+ if (!isXRP (curIssue.currency) &&
+ curIssue.account != cur->getAccountID () &&
+ curIssue.account != next->getAccountID ())
+ {
+ JLOG (j.trace()) << "Inserting implied account";
+ auto msr = make_DirectStepI (ctx(), cur->getAccountID (),
+ curIssue.account, curIssue.currency);
+ if (msr.first != tesSUCCESS)
+ return {msr.first, Strand{}};
+ result.push_back (std::move (msr.second));
+ impliedPE.emplace(STPathElement::typeAccount,
+ curIssue.account, xrpCurrency(), xrpAccount());
+ cur = &*impliedPE;
+ }
+ }
+ else if (cur->isAccount() && next->isOffer())
+ {
+ if (curIssue.account != cur->getAccountID ())
+ {
+ JLOG (j.trace()) << "Inserting implied account before offer";
+ auto msr = make_DirectStepI (ctx(), cur->getAccountID (),
+ curIssue.account, curIssue.currency);
+ if (msr.first != tesSUCCESS)
+ return {msr.first, Strand{}};
+ result.push_back (std::move (msr.second));
+ impliedPE.emplace(STPathElement::typeAccount,
+ curIssue.account, xrpCurrency(), xrpAccount());
+ cur = &*impliedPE;
+ }
+ }
+ else if (cur->isOffer() && next->isAccount())
+ {
+ if (curIssue.account != next->getAccountID () &&
+ !isXRP (next->getAccountID ()))
+ {
+ if (isXRP(curIssue))
+ {
+ if (i != normPath.size() - 2)
+ return {temBAD_PATH, Strand{}};
+ else
+ {
+ // Last step. insert xrp endpoint step
+ auto msr = make_XRPEndpointStep (ctx(), next->getAccountID());
+ if (msr.first != tesSUCCESS)
+ return {msr.first, Strand{}};
+ result.push_back(std::move(msr.second));
+ }
+ }
+ else
+ {
+ JLOG(j.trace()) << "Inserting implied account after offer";
+ auto msr = make_DirectStepI(ctx(),
+ curIssue.account, next->getAccountID(), curIssue.currency);
+ if (msr.first != tesSUCCESS)
+ return {msr.first, Strand{}};
+ result.push_back(std::move(msr.second));
+ }
+ }
+ continue;
+ }
+
+ if (!next->isOffer() &&
+ next->hasCurrency() && next->getCurrency () != curIssue.currency)
+ {
+ // Should never happen
+ assert(0);
+ return {temBAD_PATH, Strand{}};
+ }
+
+ auto s =
+ toStep (ctx (/*isLast*/ i == normPath.size () - 2), cur, next, curIssue);
+ if (s.first == tesSUCCESS)
+ result.emplace_back (std::move (s.second));
+ else
+ {
+ JLOG (j.debug()) << "toStep failed: " << s.first;
+ return {s.first, Strand{}};
+ }
+ }
+
+ auto checkStrand = [&]() -> bool {
+ auto stepAccts = [](Step const& s) -> std::pair {
+ if (auto r = s.directStepAccts())
+ return *r;
+ if (auto const r = s.bookStepBook())
+ return std::make_pair(r->in.account, r->out.account);
+ Throw(
+ tefEXCEPTION, "Step should be either a direct or book step");
+ return std::make_pair(xrpAccount(), xrpAccount());
+ };
+
+ auto curAccount = src;
+ auto curIssue = [&] {
+ auto& currency =
+ sendMaxIssue ? sendMaxIssue->currency : deliver.currency;
+ if (isXRP(currency))
+ return xrpIssue();
+ return Issue{currency, src};
+ }();
+
+ for (auto const& s : result)
+ {
+ auto const accts = stepAccts(*s);
+ if (accts.first != curAccount)
+ return false;
+
+ if (auto const b = s->bookStepBook())
+ {
+ if (curIssue != b->in)
+ return false;
+ curIssue = b->out;
+ }
+ else
+ {
+ curIssue.account = accts.second;
+ }
+
+ curAccount = accts.second;
+ }
+ if (curAccount != dst)
+ return false;
+ if (curIssue.currency != deliver.currency)
+ return false;
+ if (curIssue.account != deliver.account &&
+ curIssue.account != dst)
+ return false;
+ return true;
+ };
+
+ if (!checkStrand())
+ {
+ JLOG (j.warn()) << "Flow check strand failed";
+ assert(0);
+ return {temBAD_PATH, Strand{}};
+ }
+
+ return {tesSUCCESS, std::move (result)};
+}
+
+std::pair
+toStrand (
+ ReadView const& view,
+ AccountID const& src,
+ AccountID const& dst,
+ Issue const& deliver,
+ boost::optional const& sendMaxIssue,
+ STPath const& path,
+ bool ownerPaysTransferFee,
+ beast::Journal j)
+{
+ if (view.rules().enabled(featureToStrandV2))
+ return toStrandV2(
+ view, src, dst, deliver, sendMaxIssue, path, ownerPaysTransferFee, j);
+ else
+ return toStrandV1(
+ view, src, dst, deliver, sendMaxIssue, path, ownerPaysTransferFee, j);
+}
+
std::pair>
toStrands (
ReadView const& view,
diff --git a/src/ripple/app/paths/impl/Steps.h b/src/ripple/app/paths/impl/Steps.h
index 6a212d5d87..e37b093d2c 100644
--- a/src/ripple/app/paths/impl/Steps.h
+++ b/src/ripple/app/paths/impl/Steps.h
@@ -121,6 +121,14 @@ public:
return boost::none;
}
+ // for debugging. Return the src and dst accounts for a direct step
+ // For XRP endpoints, one of src or dst will be the root account
+ virtual boost::optional>
+ directStepAccts () const
+ {
+ return boost::none;
+ }
+
/**
If this step is a DirectStepI and the src redeems to the dst, return true,
otherwise return false.
@@ -223,6 +231,26 @@ bool operator==(Strand const& lhs, Strand const& rhs)
return true;
}
+/*
+ Normalize a path by inserting implied accounts and offers
+
+ @param src Account that is sending assets
+ @param dst Account that is receiving assets
+ @param deliver Asset the dst account will receive
+ (if issuer of deliver == dst, then accept any issuer)
+ @param sendMax Optional asset to send.
+ @param path Liquidity sources to use for this strand of the payment. The path
+ contains an ordered collection of the offer books to use and
+ accounts to ripple through.
+ @return error code and normalized path
+*/
+std::pair
+normalizePath(AccountID const& src,
+ AccountID const& dst,
+ Issue const& deliver,
+ boost::optional const& sendMaxIssue,
+ STPath const& path);
+
/*
Create a strand for the specified path
@@ -235,7 +263,6 @@ bool operator==(Strand const& lhs, Strand const& rhs)
@param path Liquidity sources to use for this strand of the payment. The path
contains an ordered collection of the offer books to use and
accounts to ripple through.
- @param addDefaultPath Determines if the default path should be considered
@param l logs to write journal messages to
@return error code and collection of strands
*/
diff --git a/src/ripple/app/paths/impl/XRPEndpointStep.cpp b/src/ripple/app/paths/impl/XRPEndpointStep.cpp
index f6c11a7df1..3fd49dc833 100644
--- a/src/ripple/app/paths/impl/XRPEndpointStep.cpp
+++ b/src/ripple/app/paths/impl/XRPEndpointStep.cpp
@@ -68,6 +68,14 @@ class XRPEndpointStep : public StepImp
return acc_;
};
+ boost::optional>
+ directStepAccts () const override
+ {
+ if (isLast_)
+ return std::make_pair(xrpAccount(), acc_);
+ return std::make_pair(acc_, xrpAccount());
+ }
+
boost::optional
cachedIn () const override
{
diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h
index 3d8d9312c9..352040295e 100644
--- a/src/ripple/protocol/Feature.h
+++ b/src/ripple/protocol/Feature.h
@@ -48,6 +48,7 @@ extern uint256 const featureTickSize;
extern uint256 const fix1368;
extern uint256 const featureEscrow;
extern uint256 const featureCryptoConditionsSuite;
+extern uint256 const featureToStrandV2;
} // ripple
diff --git a/src/ripple/protocol/STPathSet.h b/src/ripple/protocol/STPathSet.h
index c67714acef..b84a74c1b5 100644
--- a/src/ripple/protocol/STPathSet.h
+++ b/src/ripple/protocol/STPathSet.h
@@ -117,6 +117,8 @@ public:
hash_value_ = get_hash (*this);
}
+ STPathElement(STPathElement const&) = default;
+
int
getNodeType () const
{
@@ -203,8 +205,8 @@ class STPath
public:
STPath () = default;
- STPath (std::vector const& p)
- : mPath (p)
+ STPath (std::vector p)
+ : mPath (std::move(p))
{ }
std::vector::size_type
@@ -279,6 +281,10 @@ public:
return mPath[i];
}
+ void reserve(size_t s)
+ {
+ mPath.reserve(s);
+ }
private:
std::vector mPath;
};
diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp
index ef8a84c9b1..e485daf3fb 100644
--- a/src/ripple/protocol/impl/Feature.cpp
+++ b/src/ripple/protocol/impl/Feature.cpp
@@ -59,5 +59,6 @@ uint256 const featureTickSize = feature("TickSize");
uint256 const fix1368 = feature("fix1368");
uint256 const featureEscrow = feature("Escrow");
uint256 const featureCryptoConditionsSuite = feature("CryptoConditionsSuite");
+uint256 const featureToStrandV2 = feature("ToStrandV2");
} // ripple
diff --git a/src/test/app/CrossingLimits_test.cpp b/src/test/app/CrossingLimits_test.cpp
index 547882d749..e3d24a5df2 100644
--- a/src/test/app/CrossingLimits_test.cpp
+++ b/src/test/app/CrossingLimits_test.cpp
@@ -18,6 +18,7 @@
#include
#include
#include
+#include
namespace ripple {
namespace test {
@@ -41,11 +42,12 @@ private:
}
public:
+
void
- testStepLimit()
+ testStepLimit(std::initializer_list fs)
{
using namespace jtx;
- Env env(*this);
+ Env env(*this, features(fs));
auto const xrpMax = XRP(100000000000);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
@@ -74,12 +76,12 @@ public:
balance("bob", USD(0)), owners("bob", 1),
balance("dan", USD(1)), owners("dan", 2)));
}
-
+
void
- testCrossingLimit()
+ testCrossingLimit(std::initializer_list fs)
{
using namespace jtx;
- Env env(*this);
+ Env env(*this, features(fs));
auto const xrpMax = XRP(100000000000);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
@@ -105,10 +107,10 @@ public:
}
void
- testStepAndCrossingLimit()
+ testStepAndCrossingLimit(std::initializer_list fs)
{
using namespace jtx;
- Env env(*this);
+ Env env(*this, features(fs));
auto const xrpMax = XRP(100000000000);
auto const gw = Account("gateway");
auto const USD = gw["USD"];
@@ -150,9 +152,14 @@ public:
void
run()
{
- testStepLimit();
- testCrossingLimit();
- testStepAndCrossingLimit();
+ auto testAll = [this](std::initializer_list fs) {
+ testStepLimit(fs);
+ testCrossingLimit(fs);
+ testStepAndCrossingLimit(fs);
+ };
+ testAll({});
+ testAll({featureFlow});
+ testAll({featureFlow, featureToStrandV2});
}
};
diff --git a/src/test/app/DeliverMin_test.cpp b/src/test/app/DeliverMin_test.cpp
index c9173c5512..f961e94e9c 100644
--- a/src/test/app/DeliverMin_test.cpp
+++ b/src/test/app/DeliverMin_test.cpp
@@ -20,6 +20,7 @@
#include
#include
#include
+#include
namespace ripple {
namespace test {
@@ -28,7 +29,7 @@ class DeliverMin_test : public beast::unit_test::suite
{
public:
void
- test_convert_all_of_an_asset()
+ test_convert_all_of_an_asset(std::initializer_list fs)
{
testcase("Convert all of an asset using DeliverMin");
@@ -37,7 +38,7 @@ public:
auto const USD = gw["USD"];
{
- Env env(*this);
+ Env env(*this, features(fs));
env.fund(XRP(10000), "alice", "bob", "carol", gw);
env.trust(USD(100), "alice", "bob", "carol");
env(pay("alice", "bob", USD(10)), delivermin(USD(10)), ter(temBAD_AMOUNT));
@@ -60,7 +61,7 @@ public:
}
{
- Env env(*this);
+ Env env(*this, features(fs));
env.fund(XRP(10000), "alice", "bob", gw);
env.trust(USD(1000), "alice", "bob");
env(pay(gw, "bob", USD(100)));
@@ -72,7 +73,7 @@ public:
}
{
- Env env(*this);
+ Env env(*this, features(fs));
env.fund(XRP(10000), "alice", "bob", "carol", gw);
env.trust(USD(1000), "bob", "carol");
env(pay(gw, "bob", USD(200)));
@@ -90,7 +91,7 @@ public:
}
{
- Env env(*this);
+ Env env(*this, features(fs));
env.fund(XRP(10000), "alice", "bob", "carol", "dan", gw);
env.trust(USD(1000), "bob", "carol", "dan");
env(pay(gw, "bob", USD(100)));
@@ -110,7 +111,9 @@ public:
void
run()
{
- test_convert_all_of_an_asset();
+ test_convert_all_of_an_asset({});
+ test_convert_all_of_an_asset({featureFlow});
+ test_convert_all_of_an_asset({featureFlow, featureToStrandV2});
}
};
diff --git a/src/test/app/Discrepancy_test.cpp b/src/test/app/Discrepancy_test.cpp
index 0a8e5573e8..52ffb98a2a 100644
--- a/src/test/app/Discrepancy_test.cpp
+++ b/src/test/app/Discrepancy_test.cpp
@@ -24,6 +24,7 @@
#include
#include
#include
+#include
#include
namespace ripple {
@@ -36,11 +37,11 @@ class Discrepancy_test : public beast::unit_test::suite
// A payment with path and sendmax is made and the transaction is queried
// to verify that the net of balance changes match the fee charged.
void
- testXRPDiscrepancy ()
+ testXRPDiscrepancy (std::initializer_list fs)
{
testcase ("Discrepancy test : XRP Discrepancy");
using namespace test::jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
Account A1 {"A1"};
Account A2 {"A2"};
@@ -143,7 +144,9 @@ class Discrepancy_test : public beast::unit_test::suite
public:
void run ()
{
- testXRPDiscrepancy ();
+ testXRPDiscrepancy ({});
+ testXRPDiscrepancy ({featureFlow});
+ testXRPDiscrepancy ({featureFlow, featureToStrandV2});
}
};
diff --git a/src/test/app/Flow_test.cpp b/src/test/app/Flow_test.cpp
index de2121b106..1a29dba593 100644
--- a/src/test/app/Flow_test.cpp
+++ b/src/test/app/Flow_test.cpp
@@ -21,61 +21,16 @@
#include
#include
#include
+#include
#include
#include
#include
#include
#include
+
namespace ripple {
namespace test {
-struct Flow_test;
-
-struct DirectStepInfo
-{
- AccountID src;
- AccountID dst;
- Currency currency;
-};
-
-struct XRPEndpointStepInfo
-{
- AccountID acc;
-};
-
-enum class TrustFlag {freeze, auth};
-
-/*constexpr*/ std::uint32_t trustFlag (TrustFlag f, bool useHigh)
-{
- switch(f)
- {
- case TrustFlag::freeze:
- if (useHigh)
- return lsfHighFreeze;
- return lsfLowFreeze;
- case TrustFlag::auth:
- if (useHigh)
- return lsfHighAuth;
- return lsfLowAuth;
- }
- return 0; // Silence warning about end of non-void function
-}
-
-bool getTrustFlag (jtx::Env const& env,
- jtx::Account const& src,
- jtx::Account const& dst,
- Currency const& cur,
- TrustFlag flag)
-{
- if (auto sle = env.le (keylet::line (src, dst, cur)))
- {
- auto const useHigh = src.id() > dst.id();
- return sle->isFlag (trustFlag (flag, useHigh));
- }
- Throw ("No line in getTrustFlag");
- return false; // silence warning
-}
-
jtx::PrettyAmount
xrpMinusFee (jtx::Env const& env, std::int64_t xrpAmount)
{
@@ -85,311 +40,17 @@ xrpMinusFee (jtx::Env const& env, std::int64_t xrpAmount)
dropsPerXRP::value * xrpAmount - feeDrops);
};
-bool equal (std::unique_ptr const& s1,
- DirectStepInfo const& dsi)
-{
- if (!s1)
- return false;
- return test::directStepEqual (*s1, dsi.src, dsi.dst, dsi.currency);
-}
-
-bool equal (std::unique_ptr const& s1,
- XRPEndpointStepInfo const& xrpsi)
-{
- if (!s1)
- return false;
- return test::xrpEndpointStepEqual (*s1, xrpsi.acc);
-}
-
-bool equal (std::unique_ptr const& s1, ripple::Book const& bsi)
-{
- if (!s1)
- return false;
- return bookStepEqual (*s1, bsi);
-}
-
-template
-bool strandEqualHelper (Iter i)
-{
- // base case. all args processed and found equal.
- return true;
-}
-
-template
-bool strandEqualHelper (Iter i, StepInfo&& si, Args&&... args)
-{
- if (!equal (*i, std::forward (si)))
- return false;
- return strandEqualHelper (++i, std::forward (args)...);
-}
-
-template
-bool equal (Strand const& strand, Args&&... args)
-{
- if (strand.size () != sizeof...(Args))
- return false;
- if (strand.empty ())
- return true;
- return strandEqualHelper (strand.begin (), std::forward (args)...);
-}
-
struct Flow_test : public beast::unit_test::suite
{
- // Account path element
- static auto APE(AccountID const& a)
+ static bool hasFeature(uint256 const& feat, std::initializer_list args)
{
- return STPathElement (
- STPathElement::typeAccount, a, xrpCurrency (), xrpAccount ());
- };
-
- // Issue path element
- static auto IPE(Issue const& iss)
- {
- return STPathElement (
- STPathElement::typeCurrency | STPathElement::typeIssuer,
- xrpAccount (), iss.currency, iss.account);
- };
-
- // Issuer path element
- static auto IAPE(AccountID const& account)
- {
- return STPathElement (
- STPathElement::typeIssuer,
- xrpAccount (), xrpCurrency (), account);
- };
-
- // Currency path element
- static auto CPE(Currency const& c)
- {
- return STPathElement (
- STPathElement::typeCurrency, xrpAccount (), c, xrpAccount ());
- };
-
- void testToStrand ()
- {
- testcase ("To Strand");
-
- using namespace jtx;
- auto const alice = Account ("alice");
- auto const bob = Account ("bob");
- auto const carol = Account ("carol");
- auto const gw = Account ("gw");
-
- auto const USD = gw["USD"];
- auto const EUR = gw["EUR"];
-
- auto const eurC = EUR.currency;
- auto const usdC = USD.currency;
-
- using D = DirectStepInfo;
- using B = ripple::Book;
- using XRPS = XRPEndpointStepInfo;
-
- auto test = [&, this](jtx::Env& env, Issue const& deliver,
- boost::optional const& sendMaxIssue, STPath const& path,
- TER expTer, auto&&... expSteps)
- {
- auto r = toStrand (*env.current (), alice, bob,
- deliver, sendMaxIssue, path, true, env.app ().logs ().journal ("Flow"));
- BEAST_EXPECT(r.first == expTer);
- if (sizeof...(expSteps))
- BEAST_EXPECT(equal (
- r.second, std::forward (expSteps)...));
- };
-
- {
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
- env.fund (XRP (10000), alice, bob, carol, gw);
-
- test (env, USD, boost::none, STPath(), terNO_LINE);
-
- env.trust (USD (1000), alice, bob, carol);
- test (env, USD, boost::none, STPath(), tecPATH_DRY);
-
- env (pay (gw, alice, USD (100)));
- env (pay (gw, carol, USD (100)));
-
- // Insert implied account
- test (env, USD, boost::none, STPath(), tesSUCCESS,
- D{alice, gw, usdC}, D{gw, bob, usdC});
- env.trust (EUR (1000), alice, bob);
-
- // Insert implied offer
- test (env, EUR, USD.issue (), STPath(), tesSUCCESS,
- D{alice, gw, usdC}, B{USD, EUR}, D{gw, bob, eurC});
-
- // Path with explicit offer
- test (env, EUR, USD.issue (), STPath ({IPE (EUR)}),
- tesSUCCESS, D{alice, gw, usdC}, B{USD, EUR}, D{gw, bob, eurC});
-
- // Path with offer that changes issuer only
- env.trust (carol["USD"] (1000), bob);
- test (env, carol["USD"], USD.issue (), STPath ({IAPE (carol)}),
- tesSUCCESS,
- D{alice, gw, usdC}, B{USD, carol["USD"]}, D{carol, bob, usdC});
-
- // Path with XRP src currency
- test (env, USD, xrpIssue (), STPath ({IPE (USD)}), tesSUCCESS,
- XRPS{alice}, B{XRP, USD}, D{gw, bob, usdC});
-
- // Path with XRP dst currency
- test (env, xrpIssue(), USD.issue (), STPath ({IPE (XRP)}),
- tesSUCCESS, D{alice, gw, usdC}, B{USD, XRP}, XRPS{bob});
-
- // Path with XRP cross currency bridged payment
- test (env, EUR, USD.issue (), STPath ({CPE (xrpCurrency ())}),
- tesSUCCESS,
- D{alice, gw, usdC}, B{USD, XRP}, B{XRP, EUR}, D{gw, bob, eurC});
-
- // XRP -> XRP transaction can't include a path
- test (env, XRP, boost::none, STPath ({APE (carol)}), temBAD_PATH);
-
- {
- // The root account can't be the src or dst
- auto flowJournal = env.app ().logs ().journal ("Flow");
- {
- // The root account can't be the dst
- auto r = toStrand (*env.current (), alice,
- xrpAccount (), XRP, USD.issue (), STPath (), true, flowJournal);
- BEAST_EXPECT(r.first == temBAD_PATH);
- }
- {
- // The root account can't be the src
- auto r =
- toStrand (*env.current (), xrpAccount (),
- alice, XRP, boost::none, STPath (), true, flowJournal);
- BEAST_EXPECT(r.first == temBAD_PATH);
- }
- {
- // The root account can't be the src
- auto r = toStrand (*env.current (),
- noAccount (), bob, USD, boost::none, STPath (), true, flowJournal);
- BEAST_EXPECT(r.first == terNO_ACCOUNT);
- }
- }
-
- // Create an offer with the same in/out issue
- test (env, EUR, USD.issue (), STPath ({IPE (USD), IPE (EUR)}),
- temBAD_PATH);
-
- // Path element with type zero
- test (env, USD, boost::none,
- STPath ({STPathElement (
- 0, xrpAccount (), xrpCurrency (), xrpAccount ())}),
- temBAD_PATH);
-
- // The same account can't appear more than once on a path
- // `gw` will be used from alice->carol and implied between carol
- // and bob
- test (env, USD, boost::none, STPath ({APE (gw), APE (carol)}),
- temBAD_PATH_LOOP);
-
- // The same offer can't appear more than once on a path
- test (env, EUR, USD.issue (), STPath ({IPE (EUR), IPE (USD), IPE (EUR)}),
- temBAD_PATH_LOOP);
- }
-
- {
- // cannot have more than one offer with the same output issue
-
- using namespace jtx;
- Env env (*this, features (featureFlow), features(featureOwnerPaysFee));
-
- env.fund (XRP (10000), alice, bob, carol, gw);
- env.trust (USD (10000), alice, bob, carol);
- env.trust (EUR (10000), alice, bob, carol);
-
- env (pay (gw, bob, USD (100)));
- env (pay (gw, bob, EUR (100)));
-
- env (offer (bob, XRP (100), USD (100)));
- env (offer (bob, USD (100), EUR (100)), txflags (tfPassive));
- env (offer (bob, EUR (100), USD (100)), txflags (tfPassive));
-
- // payment path: XRP -> XRP/USD -> USD/EUR -> EUR/USD
- env (pay (alice, carol, USD (100)), path (~USD, ~EUR, ~USD),
- sendmax (XRP (200)), txflags (tfNoRippleDirect),
- ter (temBAD_PATH_LOOP));
- }
-
- {
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
- env.fund (XRP (10000), alice, bob, noripple (gw));
- env.trust (USD (1000), alice, bob);
- env (pay (gw, alice, USD (100)));
- test (env, USD, boost::none, STPath (), terNO_RIPPLE);
- }
-
- {
- // check global freeze
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
- env.fund (XRP (10000), alice, bob, gw);
- env.trust (USD (1000), alice, bob);
- env (pay (gw, alice, USD (100)));
-
- // Account can still issue payments
- env(fset(alice, asfGlobalFreeze));
- test (env, USD, boost::none, STPath (), tesSUCCESS);
- env(fclear(alice, asfGlobalFreeze));
- test (env, USD, boost::none, STPath (), tesSUCCESS);
-
- // Account can not issue funds
- env(fset(gw, asfGlobalFreeze));
- test (env, USD, boost::none, STPath (), terNO_LINE);
- env(fclear(gw, asfGlobalFreeze));
- test (env, USD, boost::none, STPath (), tesSUCCESS);
-
- // Account can not receive funds
- env(fset(bob, asfGlobalFreeze));
- test (env, USD, boost::none, STPath (), terNO_LINE);
- env(fclear(bob, asfGlobalFreeze));
- test (env, USD, boost::none, STPath (), tesSUCCESS);
- }
- {
- // Freeze between gw and alice
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
- env.fund (XRP (10000), alice, bob, gw);
- env.trust (USD (1000), alice, bob);
- env (pay (gw, alice, USD (100)));
- test (env, USD, boost::none, STPath (), tesSUCCESS);
- env (trust (gw, alice["USD"] (0), tfSetFreeze));
- BEAST_EXPECT(getTrustFlag (env, gw, alice, usdC, TrustFlag::freeze));
- test (env, USD, boost::none, STPath (), terNO_LINE);
- }
- {
- // check no auth
- // An account may require authorization to receive IOUs from an
- // issuer
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
- env.fund (XRP (10000), alice, bob, gw);
- env (fset (gw, asfRequireAuth));
- env.trust (USD (1000), alice, bob);
- // Authorize alice but not bob
- env (trust (gw, alice ["USD"] (1000), tfSetfAuth));
- BEAST_EXPECT(getTrustFlag (env, gw, alice, usdC, TrustFlag::auth));
- env (pay (gw, alice, USD (100)));
- env.require (balance (alice, USD (100)));
- test (env, USD, boost::none, STPath (), terNO_AUTH);
-
- // Check pure issue redeem still works
- auto r = toStrand (*env.current (), alice, gw, USD,
- boost::none, STPath (), true, env.app ().logs ().journal ("Flow"));
- BEAST_EXPECT(r.first == tesSUCCESS);
- BEAST_EXPECT(equal (r.second, D{alice, gw, usdC}));
- }
- {
- // Check path with sendMax and node with correct sendMax already set
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
- env.fund (XRP (10000), alice, bob, gw);
- env.trust (USD (1000), alice, bob);
- env.trust (EUR (1000), alice, bob);
- env (pay (gw, alice, EUR (100)));
- auto const path = STPath ({STPathElement (STPathElement::typeAll,
- EUR.account, EUR.currency, EUR.account)});
- test (env, USD, EUR.issue(), path, tesSUCCESS);
- }
+ for(auto const& f : args)
+ if (f == feat)
+ return true;
+ return false;
}
- void testDirectStep ()
+
+ void testDirectStep (std::initializer_list fs)
{
testcase ("Direct Step");
@@ -407,7 +68,7 @@ struct Flow_test : public beast::unit_test::suite
auto const USD = gw["USD"];
{
// Pay USD, trivial path
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, gw);
env.trust (USD (1000), alice, bob);
@@ -417,7 +78,7 @@ struct Flow_test : public beast::unit_test::suite
}
{
// XRP transfer
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob);
env (pay (alice, bob, XRP (100)));
@@ -426,7 +87,7 @@ struct Flow_test : public beast::unit_test::suite
}
{
// Partial payments
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, gw);
env.trust (USD (1000), alice, bob);
@@ -440,7 +101,7 @@ struct Flow_test : public beast::unit_test::suite
}
{
// Pay by rippling through accounts, use path finder
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, carol, dan);
env.trust (USDA (10), bob);
@@ -455,7 +116,7 @@ struct Flow_test : public beast::unit_test::suite
{
// Pay by rippling through accounts, specify path
// and charge a transfer fee
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, carol, dan);
env.trust (USDA (10), bob);
@@ -473,7 +134,7 @@ struct Flow_test : public beast::unit_test::suite
{
// Pay by rippling through accounts, specify path and transfer fee
// Test that the transfer fee is not charged when alice issues
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, carol, dan);
env.trust (USDA (10), bob);
@@ -489,7 +150,7 @@ struct Flow_test : public beast::unit_test::suite
{
// test best quality path is taken
// Paths: A->B->D->E ; A->C->D->E
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, carol, dan, erin);
env.trust (USDA (10), bob, carol);
@@ -510,7 +171,7 @@ struct Flow_test : public beast::unit_test::suite
}
{
// Limit quality
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, carol);
env.trust (USDA (10), bob);
@@ -526,7 +187,7 @@ struct Flow_test : public beast::unit_test::suite
}
}
- void testLineQuality ()
+ void testLineQuality (std::initializer_list fs)
{
testcase ("Line Quality");
@@ -543,66 +204,63 @@ struct Flow_test : public beast::unit_test::suite
// Dan -> Bob -> Alice -> Carol; vary bobDanQIn and bobAliceQOut
for (auto bobDanQIn : {80, 100, 120})
for (auto bobAliceQOut : {80, 100, 120})
- for (auto const& f : {feature ("nullFeature"), featureFlow})
- {
- if (f != featureFlow && bobDanQIn < 100 && bobAliceQOut < 100)
- continue; // Bug in flow v1
- Env env (*this, features (f));
- env.fund (XRP (10000), alice, bob, carol, dan);
- env (trust (bob, USDD (100)), qualityInPercent (bobDanQIn));
- env (trust (bob, USDA (100)),
- qualityOutPercent (bobAliceQOut));
- env (trust (carol, USDA (100)));
+ {
+ if (!hasFeature(featureFlow, fs) && bobDanQIn < 100 &&
+ bobAliceQOut < 100)
+ continue; // Bug in flow v1
+ Env env(*this, features(fs));
+ env.fund(XRP(10000), alice, bob, carol, dan);
+ env(trust(bob, USDD(100)), qualityInPercent(bobDanQIn));
+ env(trust(bob, USDA(100)), qualityOutPercent(bobAliceQOut));
+ env(trust(carol, USDA(100)));
- env (pay (alice, bob, USDA (100)));
- env.require (balance (bob, USDA (100)));
- env (pay (dan, carol, USDA (10)), path (bob),
- sendmax (USDD (100)), txflags (tfNoRippleDirect));
- env.require (balance (bob, USDA (90)));
- if (bobAliceQOut > bobDanQIn)
- env.require (
- balance (bob, USDD (10.0 * double(bobAliceQOut) /
- double(bobDanQIn))));
- else
- env.require (balance (bob, USDD (10)));
- env.require (balance (carol, USDA (10)));
- }
+ env(pay(alice, bob, USDA(100)));
+ env.require(balance(bob, USDA(100)));
+ env(pay(dan, carol, USDA(10)),
+ path(bob), sendmax(USDD(100)), txflags(tfNoRippleDirect));
+ env.require(balance(bob, USDA(90)));
+ if (bobAliceQOut > bobDanQIn)
+ env.require(balance(
+ bob,
+ USDD(10.0 * double(bobAliceQOut) / double(bobDanQIn))));
+ else
+ env.require(balance(bob, USDD(10)));
+ env.require(balance(carol, USDA(10)));
+ }
// bob -> alice -> carol; vary carolAliceQIn
for (auto carolAliceQIn : {80, 100, 120})
- for (auto const& f : {feature ("nullFeature"), featureFlow})
- {
- Env env (*this, features (f));
- env.fund (XRP (10000), alice, bob, carol);
- env (trust (bob, USDA (10)));
- env (trust (carol, USDA (10)), qualityInPercent (carolAliceQIn));
+ {
+ Env env(*this, features(fs));
+ env.fund(XRP(10000), alice, bob, carol);
+ env(trust(bob, USDA(10)));
+ env(trust(carol, USDA(10)), qualityInPercent(carolAliceQIn));
- env (pay (alice, bob, USDA (10)));
- env.require (balance (bob, USDA (10)));
- env (pay (bob, carol, USDA (5)), sendmax (USDA (10)));
- auto const effectiveQ =
- carolAliceQIn > 100 ? 1.0 : carolAliceQIn / 100.0;
- env.require (balance (bob, USDA (10.0 - 5.0 / effectiveQ)));
- }
+ env(pay(alice, bob, USDA(10)));
+ env.require(balance(bob, USDA(10)));
+ env(pay(bob, carol, USDA(5)), sendmax(USDA(10)));
+ auto const effectiveQ =
+ carolAliceQIn > 100 ? 1.0 : carolAliceQIn / 100.0;
+ env.require(balance(bob, USDA(10.0 - 5.0 / effectiveQ)));
+ }
// bob -> alice -> carol; bobAliceQOut varies.
for (auto bobAliceQOut : {80, 100, 120})
- for (auto const& f : {feature ("nullFeature"), featureFlow})
- {
- Env env (*this, features (f));
- env.fund (XRP (10000), alice, bob, carol);
- env (trust (bob, USDA (10)), qualityOutPercent (bobAliceQOut));
- env (trust (carol, USDA (10)));
+ {
+ Env env(*this, features(fs));
+ env.fund(XRP(10000), alice, bob, carol);
+ env(trust(bob, USDA(10)), qualityOutPercent(bobAliceQOut));
+ env(trust(carol, USDA(10)));
- env (pay (alice, bob, USDA (10)));
- env.require (balance (bob, USDA (10)));
- env (pay (bob, carol, USDA (5)), sendmax (USDA (5)));
- env.require (balance (carol, USDA (5)));
- env.require (balance (bob, USDA (10-5)));
- }
+ env(pay(alice, bob, USDA(10)));
+ env.require(balance(bob, USDA(10)));
+ env(pay(bob, carol, USDA(5)), sendmax(USDA(5)));
+ env.require(balance(carol, USDA(5)));
+ env.require(balance(bob, USDA(10 - 5)));
+ }
}
- void testBookStep ()
+ void testBookStep (std::initializer_list fs)
{
testcase ("Book Step");
@@ -618,7 +276,7 @@ struct Flow_test : public beast::unit_test::suite
{
// simple IOU/IOU offer
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, carol, gw);
env.trust (USD (1000), alice, bob, carol);
@@ -639,7 +297,7 @@ struct Flow_test : public beast::unit_test::suite
}
{
// simple IOU/XRP XRP/IOU offer
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, carol, gw);
env.trust (USD (1000), alice, bob, carol);
@@ -663,7 +321,7 @@ struct Flow_test : public beast::unit_test::suite
}
{
// simple XRP -> USD through offer and sendmax
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, carol, gw);
env.trust (USD (1000), alice, bob, carol);
@@ -684,7 +342,7 @@ struct Flow_test : public beast::unit_test::suite
}
{
// simple USD -> XRP through offer and sendmax
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, carol, gw);
env.trust (USD (1000), alice, bob, carol);
@@ -705,7 +363,7 @@ struct Flow_test : public beast::unit_test::suite
}
{
// test unfunded offers are removed when payment succeeds
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, carol, gw);
env.trust (USD (1000), alice, bob, carol);
@@ -751,7 +409,7 @@ struct Flow_test : public beast::unit_test::suite
// offer. When the payment fails `flow` should return the unfunded
// offer. This test is intentionally similar to the one that removes
// unfunded offers when the payment succeeds.
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, carol, gw);
env.trust (USD (1000), alice, bob, carol);
@@ -778,7 +436,15 @@ struct Flow_test : public beast::unit_test::suite
STAmount smax (BTC (61));
PaymentSandbox sb (env.current ().get (), tapNONE);
STPathSet paths;
+ auto IPE = [](Issue const& iss) {
+ return STPathElement(
+ STPathElement::typeCurrency | STPathElement::typeIssuer,
+ xrpAccount(),
+ iss.currency,
+ iss.account);
+ };
{
+
// BTC -> USD
STPath p1 ({IPE (USD.issue ())});
paths.push_back (p1);
@@ -818,8 +484,7 @@ struct Flow_test : public beast::unit_test::suite
// Without limits, the 0.4 USD would produce 1000 EUR in the forward
// pass. This test checks that the payment produces 1 EUR, as expected.
- Env env (*this, features (featureFlow),
- features (featureOwnerPaysFee));
+ Env env (*this, features (fs));
auto const closeTime = STAmountSO::soTime2 +
100 * env.closed ()->info ().closeTimeResolution;
@@ -844,7 +509,7 @@ struct Flow_test : public beast::unit_test::suite
}
}
- void testTransferRate ()
+ void testTransferRate (std::initializer_list fs)
{
testcase ("Transfer Rate");
@@ -862,7 +527,7 @@ struct Flow_test : public beast::unit_test::suite
{
// Simple payment through a gateway with a
// transfer rate
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, carol, gw);
env(rate(gw, 1.25));
@@ -874,7 +539,7 @@ struct Flow_test : public beast::unit_test::suite
}
{
// transfer rate is not charged when issuer is src or dst
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, carol, gw);
env(rate(gw, 1.25));
@@ -886,7 +551,7 @@ struct Flow_test : public beast::unit_test::suite
}
{
// transfer fee on an offer
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, carol, gw);
env(rate(gw, 1.25));
@@ -904,7 +569,7 @@ struct Flow_test : public beast::unit_test::suite
{
// Transfer fee two consecutive offers
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, carol, gw);
env(rate(gw, 1.25));
@@ -927,7 +592,7 @@ struct Flow_test : public beast::unit_test::suite
{
// First pass through a strand redeems, second pass issues, no offers
// limiting step is not an endpoint
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
auto const USDA = alice["USD"];
auto const USDB = bob["USD"];
@@ -947,7 +612,7 @@ struct Flow_test : public beast::unit_test::suite
{
// First pass through a strand redeems, second pass issues, through an offer
// limiting step is not an endpoint
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
auto const USDA = alice["USD"];
auto const USDB = bob["USD"];
Account const dan ("dan");
@@ -974,7 +639,7 @@ struct Flow_test : public beast::unit_test::suite
{
// Offer where the owner is also the issuer, owner pays fee
- Env env (*this, features(featureFlow), features(featureOwnerPaysFee));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, gw);
env(rate(gw, 1.25));
@@ -986,9 +651,10 @@ struct Flow_test : public beast::unit_test::suite
balance (alice, xrpMinusFee(env, 10000-100)),
balance (bob, USD (100)));
}
+ if (!hasFeature(featureOwnerPaysFee, fs))
{
// Offer where the owner is also the issuer, sender pays fee
- Env env (*this, features(featureFlow));
+ Env env (*this, features(fs));
env.fund (XRP (10000), alice, bob, gw);
env(rate(gw, 1.25));
@@ -1003,8 +669,10 @@ struct Flow_test : public beast::unit_test::suite
}
void
- testFalseDryHelper (jtx::Env& env)
+ testFalseDry(std::initializer_list fs)
{
+ testcase ("falseDryChanges");
+
using namespace jtx;
auto const gw = Account ("gateway");
@@ -1014,6 +682,8 @@ struct Flow_test : public beast::unit_test::suite
Account const bob ("bob");
Account const carol ("carol");
+ Env env (*this, features (fs));
+
auto const closeTime = amendmentRIPD1141SoTime() +
100 * env.closed ()->info ().closeTimeResolution;
env.close (closeTime);
@@ -1045,21 +715,6 @@ struct Flow_test : public beast::unit_test::suite
BEAST_EXPECT(carolUSD > USD (0) && carolUSD < USD (50));
}
- void
- testFalseDry ()
- {
- testcase ("falseDryChanges");
- using namespace jtx;
- {
- Env env (*this, features (featureFlow));
- testFalseDryHelper (env);
- }
- {
- Env env (*this);
- testFalseDryHelper (env);
- }
- }
-
void
testLimitQuality ()
{
@@ -1123,7 +778,7 @@ struct Flow_test : public beast::unit_test::suite
}
void
- testSelfPayment1()
+ testSelfPayment1(std::initializer_list fs)
{
testcase ("Self-payment 1");
@@ -1140,7 +795,7 @@ struct Flow_test : public beast::unit_test::suite
auto const USD = gw1["USD"];
auto const EUR = gw2["EUR"];
- Env env (*this, features (featureFlow));
+ Env env (*this, features (fs));
auto const closeTime =
amendmentRIPD1141SoTime () + 100 * env.closed ()->info ().closeTimeResolution;
@@ -1199,7 +854,7 @@ struct Flow_test : public beast::unit_test::suite
}
void
- testSelfPayment2()
+ testSelfPayment2(std::initializer_list fs)
{
testcase ("Self-payment 2");
@@ -1214,7 +869,7 @@ struct Flow_test : public beast::unit_test::suite
auto const USD = gw1["USD"];
auto const EUR = gw2["EUR"];
- Env env (*this, features (featureFlow));
+ Env env (*this, features (fs));
auto const closeTime =
amendmentRIPD1141SoTime () + 100 * env.closed ()->info ().closeTimeResolution;
@@ -1271,7 +926,7 @@ struct Flow_test : public beast::unit_test::suite
BEAST_EXPECT(offer[sfTakerPays] == USD (495));
}
}
- void testSelfFundedXRPEndpoint (bool consumeOffer)
+ void testSelfFundedXRPEndpoint (bool consumeOffer, std::initializer_list fs)
{
// Test that the deferred credit table is not bypassed for
// XRPEndpointSteps. If the account in the first step is sending XRP and
@@ -1282,7 +937,7 @@ struct Flow_test : public beast::unit_test::suite
using namespace jtx;
- Env env(*this, features(featureFlow));
+ Env env(*this, features(fs));
// Need new behavior from `accountHolds`
auto const closeTime = amendmentRIPD1141SoTime() +
@@ -1305,7 +960,7 @@ struct Flow_test : public beast::unit_test::suite
txflags(tfPartialPayment | tfNoRippleDirect));
}
- void testUnfundedOffer (bool withFix)
+ void testUnfundedOffer (bool withFix, std::initializer_list fs)
{
testcase(std::string("Unfunded Offer ") +
(withFix ? "with fix" : "without fix"));
@@ -1313,7 +968,7 @@ struct Flow_test : public beast::unit_test::suite
using namespace jtx;
{
// Test reverse
- Env env(*this, features(featureFlow));
+ Env env(*this, features(fs));
auto closeTime = amendmentRIPD1298SoTime();
if (withFix)
closeTime += env.closed()->info().closeTimeResolution;
@@ -1345,7 +1000,7 @@ struct Flow_test : public beast::unit_test::suite
}
{
// Test forward
- Env env(*this, features(featureFlow));
+ Env env(*this, features(fs));
auto closeTime = amendmentRIPD1298SoTime();
if (withFix)
closeTime += env.closed()->info().closeTimeResolution;
@@ -1379,14 +1034,13 @@ struct Flow_test : public beast::unit_test::suite
}
}
- template
void
- testReexecuteDirectStep(Features&&... fs)
+ testReexecuteDirectStep(std::initializer_list fs)
{
testcase("ReexecuteDirectStep");
using namespace jtx;
- Env env(*this, features(fs)...);
+ Env env(*this, features(fs));
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -1488,22 +1142,32 @@ struct Flow_test : public beast::unit_test::suite
void run() override
{
- testDirectStep ();
- testLineQuality();
- testBookStep ();
- testTransferRate ();
- testToStrand ();
- testFalseDry();
testLimitQuality();
- testSelfPayment1();
- testSelfPayment2();
- testSelfFundedXRPEndpoint(false);
- testSelfFundedXRPEndpoint(true);
- testUnfundedOffer(true);
- testUnfundedOffer(false);
- testReexecuteDirectStep(featureFlow, fix1368);
testRIPD1443(true);
testRIPD1443(false);
+
+ auto testWithFeats = [this](auto&&... fs)
+ {
+ testLineQuality({fs...});
+ testFalseDry({fs...});
+ if (!sizeof...(fs))
+ return;
+ testDirectStep({fs...});
+ testBookStep({fs...});
+ testDirectStep({featureOwnerPaysFee, fs...});
+ testBookStep({featureOwnerPaysFee, fs...});
+ testTransferRate({featureOwnerPaysFee, fs...});
+ testSelfPayment1({fs...});
+ testSelfPayment2({fs...});
+ testSelfFundedXRPEndpoint(false, {fs...});
+ testSelfFundedXRPEndpoint(true, {fs...});
+ testUnfundedOffer(true, {fs...});
+ testUnfundedOffer(false, {fs...});
+ testReexecuteDirectStep({fix1368, fs...});
+ };
+ testWithFeats();
+ testWithFeats(featureFlow);
+ testWithFeats(featureFlow, featureToStrandV2);
}
};
diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp
index db7fe38de6..9856175b4c 100644
--- a/src/test/app/Freeze_test.cpp
+++ b/src/test/app/Freeze_test.cpp
@@ -17,6 +17,7 @@
*/
//==============================================================================
#include
+#include
#include
#include
#include
@@ -52,12 +53,12 @@ class Freeze_test : public beast::unit_test::suite
return val.isArray() && val.size() == size;
}
- void testRippleState()
+ void testRippleState(std::initializer_list fs)
{
testcase("RippleState Freeze");
using namespace test::jtx;
- Env env(*this);
+ Env env(*this, features(fs));
Account G1 {"G1"};
Account alice {"alice"};
@@ -206,12 +207,12 @@ class Freeze_test : public beast::unit_test::suite
}
void
- testGlobalFreeze()
+ testGlobalFreeze(std::initializer_list fs)
{
testcase("Global Freeze");
using namespace test::jtx;
- Env env(*this);
+ Env env(*this, features(fs));
Account G1 {"G1"};
Account A1 {"A1"};
@@ -364,12 +365,12 @@ class Freeze_test : public beast::unit_test::suite
}
void
- testNoFreeze()
+ testNoFreeze(std::initializer_list fs)
{
testcase("No Freeze");
using namespace test::jtx;
- Env env(*this);
+ Env env(*this, features(fs));
Account G1 {"G1"};
Account A1 {"A1"};
@@ -418,12 +419,12 @@ class Freeze_test : public beast::unit_test::suite
}
void
- testOffersWhenFrozen()
+ testOffersWhenFrozen(std::initializer_list fs)
{
testcase("Offers for Frozen Trust Lines");
using namespace test::jtx;
- Env env(*this);
+ Env env(*this, features(fs));
Account G1 {"G1"};
Account A2 {"A2"};
@@ -522,10 +523,16 @@ public:
void run()
{
- testRippleState();
- testGlobalFreeze();
- testNoFreeze();
- testOffersWhenFrozen();
+ auto testAll = [this](std::initializer_list fs)
+ {
+ testRippleState(fs);
+ testGlobalFreeze(fs);
+ testNoFreeze(fs);
+ testOffersWhenFrozen(fs);
+ };
+ testAll({});
+ testAll({featureFlow});
+ testAll({featureFlow, featureToStrandV2});
}
};
diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp
index 22ac5cd561..2701302d0a 100644
--- a/src/test/app/Offer_test.cpp
+++ b/src/test/app/Offer_test.cpp
@@ -92,7 +92,7 @@ class Offer_test : public beast::unit_test::suite
}
public:
- void testRmFundedOffer ()
+ void testRmFundedOffer (std::initializer_list fs)
{
testcase ("Incorrect Removal of Funded Offers");
@@ -105,7 +105,7 @@ public:
// still funded and not used for the payment.
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
// ledger close times have a dynamic resolution depending on network
// conditions it appears the resolution in test is 10 seconds
@@ -151,12 +151,12 @@ public:
isOffer (env, carol, BTC (49), XRP (49)));
}
- void testCanceledOffer ()
+ void testCanceledOffer (std::initializer_list fs)
{
testcase ("Removing Canceled Offers");
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto const gw = Account {"gateway"};
auto const alice = Account {"alice"};
auto const USD = gw["USD"];
@@ -206,7 +206,7 @@ public:
BEAST_EXPECT(!isOffer (env, alice, XRP (222), USD (111)));
}
- void testTinyPayment ()
+ void testTinyPayment (std::initializer_list fs)
{
testcase ("Tiny payments");
@@ -221,7 +221,7 @@ public:
auto const USD = gw["USD"];
auto const EUR = gw["EUR"];
- Env env {*this};
+ Env env {*this, features(fs)};
env.fund (XRP (10000), alice, bob, carol, gw);
env.trust (USD (1000), alice, bob, carol);
@@ -248,7 +248,7 @@ public:
}
}
- void testXRPTinyPayment ()
+ void testXRPTinyPayment (std::initializer_list fs)
{
testcase ("XRP Tiny payments");
@@ -279,7 +279,10 @@ public:
for (auto withFix : {false, true})
{
- Env env {*this};
+ if (!withFix && fs.size())
+ continue;
+
+ Env env {*this, features(fs)};
auto closeTime = [&]
{
@@ -352,7 +355,7 @@ public:
}
}
- void testEnforceNoRipple ()
+ void testEnforceNoRipple (std::initializer_list fs)
{
testcase ("Enforce No Ripple");
@@ -369,7 +372,7 @@ public:
{
// No ripple with an implied account step after an offer
- Env env {*this};
+ Env env {*this, features(fs)};
auto const gw1 = Account {"gw1"};
auto const USD1 = gw1["USD"];
auto const gw2 = Account {"gw2"};
@@ -419,7 +422,7 @@ public:
}
void
- testInsufficientReserve ()
+ testInsufficientReserve (std::initializer_list fs)
{
testcase ("Insufficient Reserve");
@@ -442,7 +445,7 @@ public:
// No crossing:
{
- Env env {*this};
+ Env env {*this, features(fs)};
env.fund (XRP (1000000), gw);
auto const f = env.current ()->fees ().base;
@@ -490,7 +493,7 @@ public:
// if an offer were added. Attempt to sell IOUs to
// buy XRP. If it fully crosses, we succeed.
{
- Env env {*this};
+ Env env {*this, features(fs)};
env.fund (XRP (1000000), gw);
auto const f = env.current ()->fees ().base;
@@ -522,7 +525,7 @@ public:
}
void
- testFillModes ()
+ testFillModes (std::initializer_list fs)
{
testcase ("Fill Modes");
@@ -537,7 +540,7 @@ public:
// Fill or Kill - unless we fully cross, just charge
// a fee and not place the offer on the books:
{
- Env env {*this};
+ Env env {*this, features(fs)};
env.fund (startBalance, gw);
auto const f = env.current ()->fees ().base;
@@ -579,7 +582,7 @@ public:
// Immediate or Cancel - cross as much as possible
// and add nothing on the books:
{
- Env env {*this};
+ Env env {*this, features(fs)};
env.fund (startBalance, gw);
auto const f = env.current ()->fees ().base;
@@ -632,7 +635,7 @@ public:
}
void
- testMalformed()
+ testMalformed(std::initializer_list fs)
{
testcase ("Malformed Detection");
@@ -643,7 +646,7 @@ public:
auto const alice = Account {"alice"};
auto const USD = gw["USD"];
- Env env {*this};
+ Env env {*this, features(fs)};
env.fund (startBalance, gw);
env.fund (startBalance, alice);
@@ -726,7 +729,7 @@ public:
}
void
- testExpiration()
+ testExpiration(std::initializer_list fs)
{
testcase ("Offer Expiration");
@@ -799,7 +802,7 @@ public:
}
void
- testUnfundedCross()
+ testUnfundedCross(std::initializer_list fs)
{
testcase ("Unfunded Crossing");
@@ -860,7 +863,7 @@ public:
}
void
- testSelfCross(bool use_partner)
+ testSelfCross(bool use_partner, std::initializer_list fs)
{
testcase (std::string("Self-crossing") +
(use_partner ? ", with partner account" : ""));
@@ -872,7 +875,7 @@ public:
auto const USD = gw["USD"];
auto const BTC = gw["BTC"];
- Env env {*this};
+ Env env {*this, features(fs)};
env.fund (XRP (10000), gw);
if (use_partner)
{
@@ -964,7 +967,7 @@ public:
}
void
- testNegativeBalance()
+ testNegativeBalance(std::initializer_list fs)
{
// This test creates an offer test for negative balance
// with transfer fees and miniscule funds.
@@ -972,7 +975,7 @@ public:
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto const gw = Account {"gateway"};
auto const alice = Account {"alice"};
auto const bob = Account {"bob"};
@@ -1041,7 +1044,7 @@ public:
}
void
- testOfferCrossWithXRP(bool reverse_order)
+ testOfferCrossWithXRP(bool reverse_order, std::initializer_list fs)
{
testcase (std::string("Offer Crossing with XRP, ") +
(reverse_order ? "Reverse" : "Normal") +
@@ -1049,7 +1052,7 @@ public:
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto const gw = Account {"gateway"};
auto const alice = Account {"alice"};
auto const bob = Account {"bob"};
@@ -1098,13 +1101,13 @@ public:
}
void
- testOfferCrossWithLimitOverride()
+ testOfferCrossWithLimitOverride(std::initializer_list fs)
{
testcase ("Offer Crossing with Limit Override");
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto const gw = Account {"gateway"};
auto const alice = Account {"alice"};
auto const bob = Account {"bob"};
@@ -1143,13 +1146,13 @@ public:
}
void
- testOfferAcceptThenCancel()
+ testOfferAcceptThenCancel(std::initializer_list fs)
{
testcase ("Offer Accept then Cancel.");
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto const USD = env.master["USD"];
auto const nextOfferSeq = env.seq (env.master);
@@ -1170,14 +1173,14 @@ public:
}
void
- testOfferCancelPastAndFuture()
+ testOfferCancelPastAndFuture(std::initializer_list fs)
{
testcase ("Offer Cancel Past and Future Sequence.");
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto const alice = Account {"alice"};
auto const nextOfferSeq = env.seq (env.master);
@@ -1200,13 +1203,13 @@ public:
}
void
- testCurrencyConversionEntire()
+ testCurrencyConversionEntire(std::initializer_list fs)
{
testcase ("Currency Conversion: Entire Offer");
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto const gw = Account {"gateway"};
auto const alice = Account {"alice"};
auto const bob = Account {"bob"};
@@ -1260,13 +1263,13 @@ public:
}
void
- testCurrencyConversionIntoDebt()
+ testCurrencyConversionIntoDebt(std::initializer_list fs)
{
testcase ("Currency Conversion: Offerer Into Debt");
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto const alice = Account {"alice"};
auto const bob = Account {"bob"};
auto const carol = Account {"carol"};
@@ -1288,13 +1291,13 @@ public:
}
void
- testCurrencyConversionInParts()
+ testCurrencyConversionInParts(std::initializer_list fs)
{
testcase ("Currency Conversion: In Parts");
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto const gw = Account {"gateway"};
auto const alice = Account {"alice"};
auto const bob = Account {"bob"};
@@ -1375,13 +1378,13 @@ public:
}
void
- testCrossCurrencyStartXRP()
+ testCrossCurrencyStartXRP(std::initializer_list fs)
{
testcase ("Cross Currency Payment: Start with XRP");
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto const gw = Account {"gateway"};
auto const alice = Account {"alice"};
auto const bob = Account {"bob"};
@@ -1414,13 +1417,13 @@ public:
}
void
- testCrossCurrencyEndXRP()
+ testCrossCurrencyEndXRP(std::initializer_list fs)
{
testcase ("Cross Currency Payment: End with XRP");
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto const gw = Account {"gateway"};
auto const alice = Account {"alice"};
auto const bob = Account {"bob"};
@@ -1461,13 +1464,13 @@ public:
}
void
- testCrossCurrencyBridged()
+ testCrossCurrencyBridged(std::initializer_list fs)
{
testcase ("Cross Currency Payment: Bridged");
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto const gw1 = Account {"gateway_1"};
auto const gw2 = Account {"gateway_2"};
auto const alice = Account {"alice"};
@@ -1525,13 +1528,13 @@ public:
}
void
- testOfferFeesConsumeFunds()
+ testOfferFeesConsumeFunds(std::initializer_list fs)
{
testcase ("Offer Fees Consume Funds");
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto const gw1 = Account {"gateway_1"};
auto const gw2 = Account {"gateway_2"};
auto const gw3 = Account {"gateway_3"};
@@ -1578,13 +1581,13 @@ public:
}
void
- testOfferCreateThenCross()
+ testOfferCreateThenCross(std::initializer_list fs)
{
testcase ("Offer Create, then Cross");
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto const gw = Account {"gateway"};
auto const alice = Account {"alice"};
auto const bob = Account {"bob"};
@@ -1613,13 +1616,13 @@ public:
}
void
- testSellFlagBasic()
+ testSellFlagBasic(std::initializer_list fs)
{
testcase ("Offer tfSell: Basic Sell");
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto const gw = Account {"gateway"};
auto const alice = Account {"alice"};
auto const bob = Account {"bob"};
@@ -1654,13 +1657,13 @@ public:
}
void
- testSellFlagExceedLimit()
+ testSellFlagExceedLimit(std::initializer_list fs)
{
testcase ("Offer tfSell: 2x Sell Exceed Limit");
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto const gw = Account {"gateway"};
auto const alice = Account {"alice"};
auto const bob = Account {"bob"};
@@ -1697,13 +1700,13 @@ public:
}
void
- testGatewayCrossCurrency()
+ testGatewayCrossCurrency(std::initializer_list fs)
{
testcase ("Client Issue #535: Gateway Cross Currency");
using namespace jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto const gw = Account {"gateway"};
auto const alice = Account {"alice"};
auto const bob = Account {"bob"};
@@ -1764,7 +1767,7 @@ public:
BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-101");
}
- void testTickSize ()
+ void testTickSize (std::initializer_list fs)
{
testcase ("Tick Size");
@@ -1772,7 +1775,7 @@ public:
// Try to set tick size without enabling feature
{
- Env env {*this};
+ Env env {*this, features(fs)};
auto const gw = Account {"gateway"};
env.fund (XRP(10000), gw);
@@ -1783,7 +1786,7 @@ public:
// Try to set tick size out of range
{
- Env env {*this, features (featureTickSize)};
+ Env env {*this, features(fs), features (featureTickSize)};
auto const gw = Account {"gateway"};
env.fund (XRP(10000), gw);
@@ -1816,7 +1819,7 @@ public:
BEAST_EXPECT (! env.le(gw)->isFieldPresent (sfTickSize));
}
- Env env {*this, features (featureTickSize)};
+ Env env {*this, features(fs), features (featureTickSize)};
auto const gw = Account {"gateway"};
auto const alice = Account {"alice"};
auto const XTS = gw["XTS"];
@@ -1886,36 +1889,41 @@ public:
void run ()
{
- testCanceledOffer ();
- testRmFundedOffer ();
- testTinyPayment ();
- testXRPTinyPayment ();
- testEnforceNoRipple ();
- testInsufficientReserve ();
- testFillModes ();
- testMalformed ();
- testExpiration ();
- testUnfundedCross ();
- testSelfCross (false);
- testSelfCross (true);
- testNegativeBalance ();
- testOfferCrossWithXRP (true);
- testOfferCrossWithXRP (false);
- testOfferCrossWithLimitOverride ();
- testOfferAcceptThenCancel ();
- testOfferCancelPastAndFuture ();
- testCurrencyConversionEntire ();
- testCurrencyConversionIntoDebt ();
- testCurrencyConversionInParts ();
- testCrossCurrencyStartXRP ();
- testCrossCurrencyEndXRP ();
- testCrossCurrencyBridged ();
- testOfferFeesConsumeFunds ();
- testOfferCreateThenCross ();
- testSellFlagBasic ();
- testSellFlagExceedLimit ();
- testGatewayCrossCurrency ();
- testTickSize ();
+ auto testAll = [this](std::initializer_list fs) {
+ testCanceledOffer(fs);
+ testRmFundedOffer(fs);
+ testTinyPayment(fs);
+ testXRPTinyPayment(fs);
+ testEnforceNoRipple(fs);
+ testInsufficientReserve(fs);
+ testFillModes(fs);
+ testMalformed(fs);
+ testExpiration(fs);
+ testUnfundedCross(fs);
+ testSelfCross(false, fs);
+ testSelfCross(true, fs);
+ testNegativeBalance(fs);
+ testOfferCrossWithXRP(true, fs);
+ testOfferCrossWithXRP(false, fs);
+ testOfferCrossWithLimitOverride(fs);
+ testOfferAcceptThenCancel(fs);
+ testOfferCancelPastAndFuture(fs);
+ testCurrencyConversionEntire(fs);
+ testCurrencyConversionIntoDebt(fs);
+ testCurrencyConversionInParts(fs);
+ testCrossCurrencyStartXRP(fs);
+ testCrossCurrencyEndXRP(fs);
+ testCrossCurrencyBridged(fs);
+ testOfferFeesConsumeFunds(fs);
+ testOfferCreateThenCross(fs);
+ testSellFlagBasic(fs);
+ testSellFlagExceedLimit(fs);
+ testGatewayCrossCurrency(fs);
+ testTickSize(fs);
+ };
+ testAll({});
+ testAll({featureFlow});
+ testAll({featureFlow, featureToStrandV2});
}
};
diff --git a/src/test/app/PayStrand_test.cpp b/src/test/app/PayStrand_test.cpp
new file mode 100644
index 0000000000..c16f9d5cfa
--- /dev/null
+++ b/src/test/app/PayStrand_test.cpp
@@ -0,0 +1,1416 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2012, 2013 Ripple Labs Inc.
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace ripple {
+namespace test {
+
+struct DirectStepInfo
+{
+ AccountID src;
+ AccountID dst;
+ Currency currency;
+};
+
+struct XRPEndpointStepInfo
+{
+ AccountID acc;
+};
+
+enum class TrustFlag { freeze, auth };
+
+std::uint32_t
+trustFlag(TrustFlag f, bool useHigh)
+{
+ switch (f)
+ {
+ case TrustFlag::freeze:
+ if (useHigh)
+ return lsfHighFreeze;
+ return lsfLowFreeze;
+ case TrustFlag::auth:
+ if (useHigh)
+ return lsfHighAuth;
+ return lsfLowAuth;
+ }
+ return 0; // Silence warning about end of non-void function
+}
+
+bool
+getTrustFlag(
+ jtx::Env const& env,
+ jtx::Account const& src,
+ jtx::Account const& dst,
+ Currency const& cur,
+ TrustFlag flag)
+{
+ if (auto sle = env.le(keylet::line(src, dst, cur)))
+ {
+ auto const useHigh = src.id() > dst.id();
+ return sle->isFlag(trustFlag(flag, useHigh));
+ }
+ Throw("No line in getTrustFlag");
+ return false; // silence warning
+}
+
+bool
+equal(std::unique_ptr const& s1, DirectStepInfo const& dsi)
+{
+ if (!s1)
+ return false;
+ return test::directStepEqual(*s1, dsi.src, dsi.dst, dsi.currency);
+}
+
+bool
+equal(std::unique_ptr const& s1, XRPEndpointStepInfo const& xrpsi)
+{
+ if (!s1)
+ return false;
+ return test::xrpEndpointStepEqual(*s1, xrpsi.acc);
+}
+
+bool
+equal(std::unique_ptr const& s1, ripple::Book const& bsi)
+{
+ if (!s1)
+ return false;
+ return bookStepEqual(*s1, bsi);
+}
+
+template
+bool
+strandEqualHelper(Iter i)
+{
+ // base case. all args processed and found equal.
+ return true;
+}
+
+template
+bool
+strandEqualHelper(Iter i, StepInfo&& si, Args&&... args)
+{
+ if (!equal(*i, std::forward(si)))
+ return false;
+ return strandEqualHelper(++i, std::forward(args)...);
+}
+
+template
+bool
+equal(Strand const& strand, Args&&... args)
+{
+ if (strand.size() != sizeof...(Args))
+ return false;
+ if (strand.empty())
+ return true;
+ return strandEqualHelper(strand.begin(), std::forward(args)...);
+}
+
+STPathElement
+ape(AccountID const& a)
+{
+ return STPathElement(
+ STPathElement::typeAccount, a, xrpCurrency(), xrpAccount());
+};
+
+// Issue path element
+STPathElement
+ipe(Issue const& iss)
+{
+ return STPathElement(
+ STPathElement::typeCurrency | STPathElement::typeIssuer,
+ xrpAccount(),
+ iss.currency,
+ iss.account);
+};
+
+// Issuer path element
+STPathElement
+iape(AccountID const& account)
+{
+ return STPathElement(
+ STPathElement::typeIssuer, xrpAccount(), xrpCurrency(), account);
+};
+
+// Currency path element
+STPathElement
+cpe(Currency const& c)
+{
+ return STPathElement(
+ STPathElement::typeCurrency, xrpAccount(), c, xrpAccount());
+};
+
+// All path element
+STPathElement
+allpe(AccountID const& a, Issue const& iss)
+{
+ return STPathElement(
+ STPathElement::typeAccount | STPathElement::typeCurrency |
+ STPathElement::typeIssuer,
+ a,
+ iss.currency,
+ iss.account);
+};
+
+class ElementComboIter
+{
+ enum class SB /*state bit*/
+ { acc,
+ iss,
+ cur,
+ rootAcc,
+ rootIss,
+ xrp,
+ sameAccIss,
+ existingAcc,
+ existingCur,
+ existingIss,
+ prevAcc,
+ prevCur,
+ prevIss,
+ boundary,
+ last };
+
+ std::uint16_t state_ = 0;
+ static_assert(static_cast(SB::last) <= sizeof(decltype(state_)) * 8, "");
+ STPathElement const* prev_ = nullptr;
+ // disallow iss and cur to be specified with acc is specified (simplifies some tests)
+ bool const allowCompound_ = false;
+
+ bool
+ has(SB s) const
+ {
+ return state_ & (1 << static_cast(s));
+ }
+
+ bool
+ hasAny(std::initializer_list sb) const
+ {
+ for (auto const s : sb)
+ if (has(s))
+ return true;
+ return false;
+ }
+
+ size_t
+ count(std::initializer_list sb) const
+ {
+ size_t result=0;
+
+ for (auto const s : sb)
+ if (has(s))
+ result++;
+ return result;
+ }
+
+public:
+ explicit ElementComboIter(STPathElement const* prev = nullptr) : prev_(prev)
+ {
+ }
+
+ bool
+ valid() const
+ {
+ return
+ (allowCompound_ || !(has(SB::acc) && hasAny({SB::cur, SB::iss}))) &&
+ (!hasAny({SB::prevAcc, SB::prevCur, SB::prevIss}) || prev_) &&
+ (!hasAny({SB::rootAcc, SB::sameAccIss, SB::existingAcc, SB::prevAcc}) || has(SB::acc)) &&
+ (!hasAny({SB::rootIss, SB::sameAccIss, SB::existingIss, SB::prevIss}) || has(SB::iss)) &&
+ (!hasAny({SB::xrp, SB::existingCur, SB::prevCur}) || has(SB::cur)) &&
+ // These will be duplicates
+ (count({SB::xrp, SB::existingCur, SB::prevCur}) <= 1) &&
+ (count({SB::rootAcc, SB::existingAcc, SB::prevAcc}) <= 1) &&
+ (count({SB::rootIss, SB::existingIss, SB::rootIss}) <= 1);
+ }
+ bool
+ next()
+ {
+ if (!(has(SB::last)))
+ {
+ do
+ {
+ ++state_;
+ } while (!valid());
+ }
+ return !has(SB::last);
+ }
+
+ template <
+ class Col,
+ class AccFactory,
+ class IssFactory,
+ class CurrencyFactory>
+ void
+ emplace_into(
+ Col& col,
+ AccFactory&& accF,
+ IssFactory&& issF,
+ CurrencyFactory&& currencyF,
+ boost::optional const& existingAcc,
+ boost::optional const& existingCur,
+ boost::optional const& existingIss)
+ {
+ assert(!has(SB::last));
+
+ auto const acc = [&]() -> boost::optional {
+ if (!has(SB::acc))
+ return boost::none;
+ if (has(SB::rootAcc))
+ return xrpAccount();
+ if (has(SB::existingAcc) && existingAcc)
+ return existingAcc;
+ return accF().id();
+ }();
+ auto const iss = [&]() -> boost::optional {
+ if (!has(SB::iss))
+ return boost::none;
+ if (has(SB::rootIss))
+ return xrpAccount();
+ if (has(SB::sameAccIss))
+ return acc;
+ if (has(SB::existingIss) && existingIss)
+ return *existingIss;
+ return issF().id();
+ }();
+ auto const cur = [&]() -> boost::optional {
+ if (!has(SB::cur))
+ return boost::none;
+ if (has(SB::xrp))
+ return xrpCurrency();
+ if (has(SB::existingCur) && existingCur)
+ return *existingCur;
+ return currencyF();
+ }();
+ if (!has(SB::boundary))
+ col.emplace_back(acc, cur, iss);
+ else
+ col.emplace_back(
+ STPathElement::Type::typeBoundary,
+ acc.value_or(AccountID{}),
+ cur.value_or(Currency{}),
+ iss.value_or(AccountID{}));
+ }
+};
+
+struct ExistingElementPool
+{
+ std::vector accounts;
+ std::vector currencies;
+ std::vector currencyNames;
+
+ jtx::Account
+ getAccount(size_t id)
+ {
+ assert(id < accounts.size());
+ return accounts[id];
+ }
+
+ ripple::Currency
+ getCurrency(size_t id)
+ {
+ assert(id < currencies.size());
+ return currencies[id];
+ }
+
+ // ids from 0 through (nextAvail -1) have already been used in the
+ // path
+ size_t nextAvailAccount = 0;
+ size_t nextAvailCurrency = 0;
+
+ using ResetState = std::tuple;
+ ResetState
+ getResetState() const
+ {
+ return std::make_tuple(nextAvailAccount, nextAvailCurrency);
+ }
+
+ void
+ resetTo(ResetState const& s)
+ {
+ std::tie(nextAvailAccount, nextAvailCurrency) = s;
+ }
+
+ struct StateGuard
+ {
+ ExistingElementPool& p_;
+ ResetState state_;
+
+ StateGuard(ExistingElementPool& p) : p_{p}, state_{p.getResetState()}
+ {
+ }
+ ~StateGuard()
+ {
+ p_.resetTo(state_);
+ }
+ };
+
+ // Create the given number of accounts, and add trust lines so every
+ // account trusts every other with every currency
+ // Create an offer from every currency/account to every other
+ // currency/account; the offer owner is either the specified
+ // account or the issuer of the "taker gets" account
+ void
+ setupEnv(
+ jtx::Env& env,
+ size_t numAct,
+ size_t numCur,
+ boost::optional const& offererIndex)
+ {
+ using namespace jtx;
+
+ assert(!offererIndex || offererIndex < numAct);
+
+ accounts.clear();
+ accounts.reserve(numAct);
+ currencies.clear();
+ currencies.reserve(numCur);
+ currencyNames.clear();
+ currencyNames.reserve(numCur);
+
+ constexpr size_t bufSize = 8;
+ char buf[bufSize];
+
+ for (size_t id = 0; id < numAct; ++id)
+ {
+ snprintf(buf, bufSize, "A%zu", id);
+ accounts.emplace_back(buf);
+ }
+
+ for (size_t id = 0; id < numCur; ++id)
+ {
+ if (id < 10)
+ snprintf(buf, bufSize, "CC%zu", id);
+ else if (id < 100)
+ snprintf(buf, bufSize, "C%zu", id);
+ else
+ snprintf(buf, bufSize, "%zu", id);
+ currencies.emplace_back(to_currency(buf));
+ currencyNames.emplace_back(buf);
+ }
+
+ for (auto const& a : accounts)
+ env.fund(XRP(100000), a);
+
+ // Every account trusts every other account with every currency
+ for (auto ai1 = accounts.begin(), aie = accounts.end(); ai1 != aie;
+ ++ai1)
+ {
+ for (auto ai2 = accounts.begin(); ai2 != aie; ++ai2)
+ {
+ if (ai1 == ai2)
+ continue;
+ for (auto const& cn : currencyNames)
+ {
+ env.trust((*ai1)[cn](1'000'000), *ai2);
+ if (ai1 > ai2)
+ {
+ // accounts with lower indexes hold balances from
+ // accounts
+ // with higher indexes
+ auto const& src = *ai1;
+ auto const& dst = *ai2;
+ env(pay(src, dst, src[cn](500000)));
+ }
+ }
+ env.close();
+ }
+ }
+
+ std::vector ious;
+ ious.reserve(numAct * numCur);
+ for (auto const& a : accounts)
+ for (auto const& cn : currencyNames)
+ ious.emplace_back(a[cn]);
+
+ // create offers from every currency to every other currency
+ for (auto takerPays = ious.begin(), ie = ious.end(); takerPays != ie;
+ ++takerPays)
+ {
+ for (auto takerGets = ious.begin(); takerGets != ie; ++takerGets)
+ {
+ if (takerPays == takerGets)
+ continue;
+ auto const owner =
+ offererIndex ? accounts[*offererIndex] : takerGets->account;
+ if (owner.id() != takerGets->account.id())
+ env(pay(takerGets->account, owner, (*takerGets)(1000)));
+
+ env(offer(owner, (*takerPays)(1000), (*takerGets)(1000)),
+ txflags(tfPassive));
+ }
+ env.close();
+ }
+
+ // create offers to/from xrp to every other ious
+ for (auto const& iou : ious)
+ {
+ auto const owner =
+ offererIndex ? accounts[*offererIndex] : iou.account;
+ env(offer(owner, iou(1000), XRP(1000)), txflags(tfPassive));
+ env(offer(owner, XRP(1000), iou(1000)), txflags(tfPassive));
+ env.close();
+ }
+ }
+
+ std::int64_t
+ totalXRP(ReadView const& v, bool incRoot)
+ {
+ std::uint64_t totalXRP = 0;
+ auto add = [&](auto const& a) {
+ // XRP balance
+ auto const sle = v.read(keylet::account(a));
+ if (!sle)
+ return;
+ auto const b = (*sle)[sfBalance];
+ totalXRP += b.mantissa();
+ };
+ for (auto const& a : accounts)
+ add(a);
+ if (incRoot)
+ add(xrpAccount());
+ return totalXRP;
+ }
+
+ // Check that the balances for all accounts for all currencies & XRP are the
+ // same
+ bool
+ checkBalances(ReadView const& v1, ReadView const& v2)
+ {
+ std::vector> diffs;
+
+ auto xrpBalance = [](ReadView const& v, ripple::Keylet const& k) {
+ auto const sle = v.read(k);
+ if (!sle)
+ return STAmount{};
+ return (*sle)[sfBalance];
+ };
+ auto lineBalance = [](ReadView const& v, ripple::Keylet const& k) {
+ auto const sle = v.read(k);
+ if (!sle)
+ return STAmount{};
+ return (*sle)[sfBalance];
+ };
+ std::uint64_t totalXRP[2];
+ for (auto ai1 = accounts.begin(), aie = accounts.end(); ai1 != aie;
+ ++ai1)
+ {
+ {
+ // XRP balance
+ auto const ak = keylet::account(*ai1);
+ auto const b1 = xrpBalance(v1, ak);
+ auto const b2 = xrpBalance(v2, ak);
+ totalXRP[0] += b1.mantissa();
+ totalXRP[1] += b2.mantissa();
+ if (b1 != b2)
+ diffs.emplace_back(b1, b2, xrpAccount(), *ai1);
+ }
+ for (auto ai2 = accounts.begin(); ai2 != aie; ++ai2)
+ {
+ if (ai1 >= ai2)
+ continue;
+ for (auto const& c : currencies)
+ {
+ // Line balance
+ auto const lk = keylet::line(*ai1, *ai2, c);
+ auto const b1 = lineBalance(v1, lk);
+ auto const b2 = lineBalance(v2, lk);
+ if (b1 != b2)
+ diffs.emplace_back(b1, b2, *ai1, *ai2);
+ }
+ }
+ }
+ return diffs.empty();
+ }
+
+ jtx::Account
+ getAvailAccount()
+ {
+ return getAccount(nextAvailAccount++);
+ }
+
+ ripple::Currency
+ getAvailCurrency()
+ {
+ return getCurrency(nextAvailCurrency++);
+ }
+
+ template
+ void
+ for_each_element_pair(
+ STAmount const& sendMax,
+ STAmount const& deliver,
+ std::vector const& prefix,
+ std::vector const& suffix,
+ boost::optional const& existingAcc,
+ boost::optional const& existingCur,
+ boost::optional const& existingIss,
+ F&& f)
+ {
+ auto accF = [&] { return this->getAvailAccount(); };
+ auto issF = [&] { return this->getAvailAccount(); };
+ auto currencyF = [&] { return this->getAvailCurrency(); };
+
+ STPathElement const* prevOuter =
+ prefix.empty() ? nullptr : &prefix.back();
+ ElementComboIter outer(prevOuter);
+
+ std::vector outerResult;
+ std::vector result;
+ auto const resultSize = prefix.size() + suffix.size() + 2;
+ outerResult.reserve(resultSize);
+ result.reserve(resultSize);
+ while (outer.next())
+ {
+ StateGuard og{*this};
+ outerResult = prefix;
+ outer.emplace_into(
+ outerResult,
+ accF,
+ issF,
+ currencyF,
+ existingAcc,
+ existingCur,
+ existingIss);
+ STPathElement const* prevInner = &outerResult.back();
+ ElementComboIter inner(prevInner);
+ while (inner.next())
+ {
+ StateGuard ig{*this};
+ result = outerResult;
+ inner.emplace_into(
+ result,
+ accF,
+ issF,
+ currencyF,
+ existingAcc,
+ existingCur,
+ existingIss);
+ result.insert(result.end(), suffix.begin(), suffix.end());
+ f(sendMax, deliver, result);
+ }
+ };
+ }
+};
+
+struct PayStrand_test : public beast::unit_test::suite
+{
+ static bool hasFeature(uint256 const& feat)
+ {
+ return false;
+ }
+
+ static bool hasFeature(uint256 const& feat, std::initializer_list args)
+ {
+ for(auto const& f : args)
+ if (f == feat)
+ return true;
+ return false;
+ }
+
+ // Test every combination of element type pairs on a path
+ void
+ testAllPairs()
+ {
+ testcase("All pairs");
+ using namespace jtx;
+ using RippleCalc = ::ripple::path::RippleCalc;
+
+ ExistingElementPool eep;
+ Env env(*this, features(featureToStrandV2));
+
+ auto const closeTime = amendmentRIPD1298SoTime() +
+ 100 * env.closed()->info().closeTimeResolution;
+ env.close(closeTime);
+ eep.setupEnv(env, /*numAcc*/ 9, /*numCur*/ 6, boost::none);
+ env.close();
+
+ auto const src = eep.getAvailAccount();
+ auto const dst = eep.getAvailAccount();
+
+ RippleCalc::Input inputs;
+ inputs.defaultPathsAllowed = false;
+
+ auto callback = [&](
+ STAmount const& sendMax,
+ STAmount const& deliver,
+ std::vector const& p) {
+ std::array sbs{
+ {PaymentSandbox{env.current().get(), tapNONE},
+ PaymentSandbox{env.current().get(), tapNONE}}};
+ std::array rcOutputs;
+ // pay with both env1 and env2
+ // check all result and account balances match
+ // save results so can see if run out of funds or somesuch
+ STPathSet paths;
+ paths.emplace_back(p);
+ for (auto i = 0; i < 2; ++i)
+ {
+ if (i == 0)
+ env.app().config().features.insert(featureFlow);
+ else
+ env.app().config().features.erase(featureFlow);
+
+ try
+ {
+ rcOutputs[i] = RippleCalc::rippleCalculate(
+ sbs[i],
+ sendMax,
+ deliver,
+ dst,
+ src,
+ paths,
+ env.app().logs(),
+ &inputs);
+ }
+ catch (...)
+ {
+ this->fail();
+ }
+ }
+
+ // check combinations of src and dst currencies (inc xrp)
+ // Check the results
+ auto const terMatch = [&] {
+ if (rcOutputs[0].result() == rcOutputs[1].result())
+ return true;
+
+ // handle some know error code mismatches
+ if (p.empty() ||
+ !(rcOutputs[0].result() == temBAD_PATH ||
+ rcOutputs[0].result() == temBAD_PATH_LOOP))
+ return false;
+
+ if (rcOutputs[1].result() == temBAD_PATH)
+ return true;
+
+ if (rcOutputs[1].result() == terNO_LINE)
+ return true;
+
+ for (auto const& pe : p)
+ {
+ auto const t = pe.getNodeType();
+ if ((t & STPathElement::typeAccount) &&
+ t != STPathElement::typeAccount)
+ {
+ return true;
+ }
+ }
+
+ // xrp followed by offer that doesn't specify both currency and
+ // issuer (and currency is not xrp, if specifyed)
+ if (isXRP(sendMax) &&
+ !(p[0].hasCurrency() && isXRP(p[0].getCurrency())) &&
+ !(p[0].hasCurrency() && p[0].hasIssuer()))
+ {
+ return true;
+ }
+
+ for (size_t i = 0; i < p.size() - 1; ++i)
+ {
+ auto const tCur = p[i].getNodeType();
+ auto const tNext = p[i + 1].getNodeType();
+ if ((tCur & STPathElement::typeCurrency) &&
+ isXRP(p[i].getCurrency()) &&
+ (tNext & STPathElement::typeAccount) &&
+ !isXRP(p[i + 1].getAccountID()))
+ {
+ return true;
+ }
+ }
+ return false;
+ }();
+
+ this->BEAST_EXPECT(
+ terMatch && (rcOutputs[0].result() == tesSUCCESS ||
+ rcOutputs[0].result() == temBAD_PATH ||
+ rcOutputs[0].result() == temBAD_PATH_LOOP));
+ if (terMatch && rcOutputs[0].result() == tesSUCCESS)
+ this->BEAST_EXPECT(eep.checkBalances(sbs[0], sbs[1]));
+ };
+
+ std::vector prefix;
+ std::vector suffix;
+
+ for (auto srcAmtIsXRP : {false, true})
+ {
+ for (auto dstAmtIsXRP : {false, true})
+ {
+ for (auto hasPrefix : {false, true})
+ {
+ ExistingElementPool::StateGuard esg{eep};
+ prefix.clear();
+ suffix.clear();
+
+ STAmount const sendMax{
+ srcAmtIsXRP ? xrpIssue() : Issue{eep.getAvailCurrency(),
+ eep.getAvailAccount()},
+ -1, // (-1 == no limit)
+ 0};
+
+ STAmount const deliver{
+ dstAmtIsXRP ? xrpIssue() : Issue{eep.getAvailCurrency(),
+ eep.getAvailAccount()},
+ 1,
+ 0};
+
+ if (hasPrefix)
+ {
+ for(auto e0IsAccount : {false, true})
+ {
+ for (auto e1IsAccount : {false, true})
+ {
+ ExistingElementPool::StateGuard presg{eep};
+ prefix.clear();
+ auto pushElement =
+ [&prefix, &eep](bool isAccount) mutable {
+ if (isAccount)
+ prefix.emplace_back(
+ eep.getAvailAccount().id(),
+ boost::none,
+ boost::none);
+ else
+ prefix.emplace_back(
+ boost::none,
+ eep.getAvailCurrency(),
+ eep.getAvailAccount().id());
+ };
+ pushElement(e0IsAccount);
+ pushElement(e1IsAccount);
+ boost::optional existingAcc;
+ boost::optional existingCur;
+ boost::optional existingIss;
+ if (e0IsAccount)
+ {
+ existingAcc = prefix[0].getAccountID();
+ }
+ else
+ {
+ existingIss = prefix[0].getIssuerID();
+ existingCur = prefix[0].getCurrency();
+ }
+ if (e1IsAccount)
+ {
+ if (!existingAcc)
+ existingAcc = prefix[1].getAccountID();
+ }
+ else
+ {
+ if (!existingIss)
+ existingIss = prefix[1].getIssuerID();
+ if (!existingCur)
+ existingCur = prefix[1].getCurrency();
+ }
+ eep.for_each_element_pair(
+ sendMax,
+ deliver,
+ prefix,
+ suffix,
+ existingAcc,
+ existingCur,
+ existingIss,
+ callback);
+ }
+ }
+ }
+ else
+ {
+ eep.for_each_element_pair(
+ sendMax,
+ deliver,
+ prefix,
+ suffix,
+ /*existingAcc*/ boost::none,
+ /*existingCur*/ boost::none,
+ /*existingIss*/ boost::none,
+ callback);
+ }
+ }
+ }
+ }
+ }
+
+ void
+ testToStrand(std::initializer_list fs)
+ {
+ testcase("To Strand");
+
+ using namespace jtx;
+
+ auto const alice = Account("alice");
+ auto const bob = Account("bob");
+ auto const carol = Account("carol");
+ auto const gw = Account("gw");
+
+ auto const USD = gw["USD"];
+ auto const EUR = gw["EUR"];
+
+ auto const eurC = EUR.currency;
+ auto const usdC = USD.currency;
+
+ using D = DirectStepInfo;
+ using B = ripple::Book;
+ using XRPS = XRPEndpointStepInfo;
+
+ auto test = [&, this](
+ jtx::Env& env,
+ Issue const& deliver,
+ boost::optional const& sendMaxIssue,
+ STPath const& path,
+ TER expTer,
+ auto&&... expSteps) {
+ auto r = toStrand(
+ *env.current(),
+ alice,
+ bob,
+ deliver,
+ sendMaxIssue,
+ path,
+ true,
+ env.app().logs().journal("Flow"));
+ BEAST_EXPECT(r.first == expTer);
+ if (sizeof...(expSteps))
+ BEAST_EXPECT(equal(
+ r.second, std::forward(expSteps)...));
+ };
+
+ {
+ Env env(*this, features(fs));
+ env.fund(XRP(10000), alice, bob, gw);
+ env.trust(USD(1000), alice, bob);
+ env.trust(EUR(1000), alice, bob);
+ env(pay(gw, alice, EUR(100)));
+
+ {
+ STPath const path =
+ STPath({ipe(bob["USD"]), cpe(EUR.currency)});
+ auto r = toStrand(
+ *env.current(),
+ alice,
+ alice,
+ /*deliver*/ xrpIssue(),
+ /*sendMaxIssue*/ EUR.issue(),
+ path,
+ true,
+ env.app().logs().journal("Flow"));
+ BEAST_EXPECT(r.first == tesSUCCESS);
+ }
+ {
+ STPath const path = STPath({ipe(USD), cpe(xrpCurrency())});
+ auto r = toStrand(
+ *env.current(),
+ alice,
+ alice,
+ /*deliver*/ xrpIssue(),
+ /*sendMaxIssue*/ xrpIssue(),
+ path,
+ true,
+ env.app().logs().journal("Flow"));
+ BEAST_EXPECT(r.first == tesSUCCESS);
+ }
+ return;
+ };
+
+ {
+ Env env(*this, features(fs));
+ env.fund(XRP(10000), alice, bob, carol, gw);
+
+ test(env, USD, boost::none, STPath(), terNO_LINE);
+
+ env.trust(USD(1000), alice, bob, carol);
+ test(env, USD, boost::none, STPath(), tecPATH_DRY);
+
+ env(pay(gw, alice, USD(100)));
+ env(pay(gw, carol, USD(100)));
+
+ // Insert implied account
+ test(
+ env,
+ USD,
+ boost::none,
+ STPath(),
+ tesSUCCESS,
+ D{alice, gw, usdC},
+ D{gw, bob, usdC});
+ env.trust(EUR(1000), alice, bob);
+
+ // Insert implied offer
+ test(
+ env,
+ EUR,
+ USD.issue(),
+ STPath(),
+ tesSUCCESS,
+ D{alice, gw, usdC},
+ B{USD, EUR},
+ D{gw, bob, eurC});
+
+ // Path with explicit offer
+ test(
+ env,
+ EUR,
+ USD.issue(),
+ STPath({ipe(EUR)}),
+ tesSUCCESS,
+ D{alice, gw, usdC},
+ B{USD, EUR},
+ D{gw, bob, eurC});
+
+ // Path with offer that changes issuer only
+ env.trust(carol["USD"](1000), bob);
+ test(
+ env,
+ carol["USD"],
+ USD.issue(),
+ STPath({iape(carol)}),
+ tesSUCCESS,
+ D{alice, gw, usdC},
+ B{USD, carol["USD"]},
+ D{carol, bob, usdC});
+
+ // Path with XRP src currency
+ test(
+ env,
+ USD,
+ xrpIssue(),
+ STPath({ipe(USD)}),
+ tesSUCCESS,
+ XRPS{alice},
+ B{XRP, USD},
+ D{gw, bob, usdC});
+
+ // Path with XRP dst currency
+ test(
+ env,
+ xrpIssue(),
+ USD.issue(),
+ STPath({ipe(XRP)}),
+ tesSUCCESS,
+ D{alice, gw, usdC},
+ B{USD, XRP},
+ XRPS{bob});
+
+ // Path with XRP cross currency bridged payment
+ test(
+ env,
+ EUR,
+ USD.issue(),
+ STPath({cpe(xrpCurrency())}),
+ tesSUCCESS,
+ D{alice, gw, usdC},
+ B{USD, XRP},
+ B{XRP, EUR},
+ D{gw, bob, eurC});
+
+ // XRP -> XRP transaction can't include a path
+ test(env, XRP, boost::none, STPath({ape(carol)}), temBAD_PATH);
+
+ {
+ // The root account can't be the src or dst
+ auto flowJournal = env.app().logs().journal("Flow");
+ {
+ // The root account can't be the dst
+ auto r = toStrand(
+ *env.current(),
+ alice,
+ xrpAccount(),
+ XRP,
+ USD.issue(),
+ STPath(),
+ true,
+ flowJournal);
+ BEAST_EXPECT(r.first == temBAD_PATH);
+ }
+ {
+ // The root account can't be the src
+ auto r = toStrand(
+ *env.current(),
+ xrpAccount(),
+ alice,
+ XRP,
+ boost::none,
+ STPath(),
+ true,
+ flowJournal);
+ BEAST_EXPECT(r.first == temBAD_PATH);
+ }
+ {
+ // The root account can't be the src
+ auto r = toStrand(
+ *env.current(),
+ noAccount(),
+ bob,
+ USD,
+ boost::none,
+ STPath(),
+ true,
+ flowJournal);
+ BEAST_EXPECT(r.first == terNO_ACCOUNT);
+ }
+ }
+
+ // Create an offer with the same in/out issue
+ test(
+ env,
+ EUR,
+ USD.issue(),
+ STPath({ipe(USD), ipe(EUR)}),
+ temBAD_PATH);
+
+ // Path element with type zero
+ test(
+ env,
+ USD,
+ boost::none,
+ STPath({STPathElement(
+ 0, xrpAccount(), xrpCurrency(), xrpAccount())}),
+ temBAD_PATH);
+
+ // The same account can't appear more than once on a path
+ // `gw` will be used from alice->carol and implied between carol
+ // and bob
+ test(
+ env,
+ USD,
+ boost::none,
+ STPath({ape(gw), ape(carol)}),
+ temBAD_PATH_LOOP);
+
+ // The same offer can't appear more than once on a path
+ test(
+ env,
+ EUR,
+ USD.issue(),
+ STPath({ipe(EUR), ipe(USD), ipe(EUR)}),
+ temBAD_PATH_LOOP);
+ }
+
+ {
+ // cannot have more than one offer with the same output issue
+
+ using namespace jtx;
+ Env env(*this, features(fs));
+
+ env.fund(XRP(10000), alice, bob, carol, gw);
+ env.trust(USD(10000), alice, bob, carol);
+ env.trust(EUR(10000), alice, bob, carol);
+
+ env(pay(gw, bob, USD(100)));
+ env(pay(gw, bob, EUR(100)));
+
+ env(offer(bob, XRP(100), USD(100)));
+ env(offer(bob, USD(100), EUR(100)), txflags(tfPassive));
+ env(offer(bob, EUR(100), USD(100)), txflags(tfPassive));
+
+ // payment path: XRP -> XRP/USD -> USD/EUR -> EUR/USD
+ env(pay(alice, carol, USD(100)),
+ path(~USD, ~EUR, ~USD),
+ sendmax(XRP(200)),
+ txflags(tfNoRippleDirect),
+ ter(temBAD_PATH_LOOP));
+ }
+
+ {
+ Env env(*this, features(fs));
+ env.fund(XRP(10000), alice, bob, noripple(gw));
+ env.trust(USD(1000), alice, bob);
+ env(pay(gw, alice, USD(100)));
+ test(env, USD, boost::none, STPath(), terNO_RIPPLE);
+ }
+
+ {
+ // check global freeze
+ Env env(*this, features(fs));
+ env.fund(XRP(10000), alice, bob, gw);
+ env.trust(USD(1000), alice, bob);
+ env(pay(gw, alice, USD(100)));
+
+ // Account can still issue payments
+ env(fset(alice, asfGlobalFreeze));
+ test(env, USD, boost::none, STPath(), tesSUCCESS);
+ env(fclear(alice, asfGlobalFreeze));
+ test(env, USD, boost::none, STPath(), tesSUCCESS);
+
+ // Account can not issue funds
+ env(fset(gw, asfGlobalFreeze));
+ test(env, USD, boost::none, STPath(), terNO_LINE);
+ env(fclear(gw, asfGlobalFreeze));
+ test(env, USD, boost::none, STPath(), tesSUCCESS);
+
+ // Account can not receive funds
+ env(fset(bob, asfGlobalFreeze));
+ test(env, USD, boost::none, STPath(), terNO_LINE);
+ env(fclear(bob, asfGlobalFreeze));
+ test(env, USD, boost::none, STPath(), tesSUCCESS);
+ }
+ {
+ // Freeze between gw and alice
+ Env env(*this, features(fs));
+ env.fund(XRP(10000), alice, bob, gw);
+ env.trust(USD(1000), alice, bob);
+ env(pay(gw, alice, USD(100)));
+ test(env, USD, boost::none, STPath(), tesSUCCESS);
+ env(trust(gw, alice["USD"](0), tfSetFreeze));
+ BEAST_EXPECT(getTrustFlag(env, gw, alice, usdC, TrustFlag::freeze));
+ test(env, USD, boost::none, STPath(), terNO_LINE);
+ }
+ {
+ // check no auth
+ // An account may require authorization to receive IOUs from an
+ // issuer
+ Env env(*this, features(fs));
+ env.fund(XRP(10000), alice, bob, gw);
+ env(fset(gw, asfRequireAuth));
+ env.trust(USD(1000), alice, bob);
+ // Authorize alice but not bob
+ env(trust(gw, alice["USD"](1000), tfSetfAuth));
+ BEAST_EXPECT(getTrustFlag(env, gw, alice, usdC, TrustFlag::auth));
+ env(pay(gw, alice, USD(100)));
+ env.require(balance(alice, USD(100)));
+ test(env, USD, boost::none, STPath(), terNO_AUTH);
+
+ // Check pure issue redeem still works
+ auto r = toStrand(
+ *env.current(),
+ alice,
+ gw,
+ USD,
+ boost::none,
+ STPath(),
+ true,
+ env.app().logs().journal("Flow"));
+ BEAST_EXPECT(r.first == tesSUCCESS);
+ BEAST_EXPECT(equal(r.second, D{alice, gw, usdC}));
+ }
+ {
+ // Check path with sendMax and node with correct sendMax already set
+ Env env(*this, features(fs));
+ env.fund(XRP(10000), alice, bob, gw);
+ env.trust(USD(1000), alice, bob);
+ env.trust(EUR(1000), alice, bob);
+ env(pay(gw, alice, EUR(100)));
+ auto const path = STPath({STPathElement(
+ STPathElement::typeAll,
+ EUR.account,
+ EUR.currency,
+ EUR.account)});
+ test(env, USD, EUR.issue(), path, tesSUCCESS);
+ }
+
+ {
+ // last step xrp from offer
+ Env env(*this, features(fs));
+ env.fund(XRP(10000), alice, bob, gw);
+ env.trust(USD(1000), alice, bob);
+ env(pay(gw, alice, USD(100)));
+
+ // alice -> USD/XRP -> bob
+ STPath path;
+ path.emplace_back(boost::none, USD.currency, USD.account.id());
+ path.emplace_back(boost::none, xrpCurrency(), boost::none);
+
+ auto r = toStrand(
+ *env.current(),
+ alice,
+ bob,
+ XRP,
+ USD.issue(),
+ path,
+ false,
+ env.app().logs().journal("Flow"));
+ BEAST_EXPECT(r.first == tesSUCCESS);
+ BEAST_EXPECT(equal(r.second, D{alice, gw, usdC}, B{USD.issue(), xrpIssue()}, XRPS{bob}));
+ }
+ }
+
+ void
+ testRIPD1373(std::initializer_list fs)
+ {
+ using namespace jtx;
+ testcase("RIPD1373");
+
+ auto const alice = Account("alice");
+ auto const bob = Account("bob");
+ auto const carol = Account("carol");
+ auto const gw = Account("gw");
+ auto const USD = gw["USD"];
+ auto const EUR = gw["EUR"];
+
+ if (hasFeature(featureToStrandV2, fs))
+ {
+ Env env(*this, features(fs));
+ env.fund(XRP(10000), alice, bob, gw);
+
+ env.trust(USD(1000), alice, bob);
+ env.trust(EUR(1000), alice, bob);
+ env.trust(bob["USD"](1000), alice, gw);
+ env.trust(bob["EUR"](1000), alice, gw);
+
+ env(offer(bob, XRP(100), bob["USD"](100)), txflags(tfPassive));
+ env(offer(gw, XRP(100), USD(100)), txflags(tfPassive));
+
+ env(offer(bob, bob["USD"](100), bob["EUR"](100)),
+ txflags(tfPassive));
+ env(offer(gw, USD(100), EUR(100)), txflags(tfPassive));
+
+ Path const p = [&] {
+ Path result;
+ result.push_back(allpe(gw, bob["USD"]));
+ result.push_back(cpe(EUR.currency));
+ return result;
+ }();
+
+ PathSet paths(p);
+
+ env(pay(alice, alice, EUR(1)),
+ json(paths.json()),
+ sendmax(XRP(10)),
+ txflags(tfNoRippleDirect | tfPartialPayment),
+ ter(temBAD_PATH));
+ }
+
+ {
+ Env env(*this, features(fs));
+
+ env.fund(XRP(10000), alice, bob, carol, gw);
+ env.trust(USD(10000), alice, bob, carol);
+
+ env(pay(gw, bob, USD(100)));
+
+ env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
+ env(offer(bob, USD(100), XRP(100)), txflags(tfPassive));
+
+ // payment path: XRP -> XRP/USD -> USD/XRP
+ env(pay(alice, carol, XRP(100)),
+ path(~USD, ~XRP),
+ txflags(tfNoRippleDirect),
+ ter(temBAD_SEND_XRP_PATHS));
+ }
+
+ {
+ Env env(*this, features(fs));
+
+ env.fund(XRP(10000), alice, bob, carol, gw);
+ env.trust(USD(10000), alice, bob, carol);
+
+ env(pay(gw, bob, USD(100)));
+
+ env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
+ env(offer(bob, USD(100), XRP(100)), txflags(tfPassive));
+
+ // payment path: XRP -> XRP/USD -> USD/XRP
+ env(pay(alice, carol, XRP(100)),
+ path(~USD, ~XRP),
+ sendmax(XRP(200)),
+ txflags(tfNoRippleDirect),
+ ter(temBAD_SEND_XRP_MAX));
+ }
+ }
+
+ void
+ testLoop(std::initializer_list fs)
+ {
+ testcase("test loop");
+ using namespace jtx;
+
+ auto const alice = Account("alice");
+ auto const bob = Account("bob");
+ auto const carol = Account("carol");
+ auto const gw = Account("gw");
+ auto const USD = gw["USD"];
+ auto const EUR = gw["EUR"];
+ auto const CNY = gw["CNY"];
+
+ {
+ Env env(*this, features(fs));
+
+ env.fund(XRP(10000), alice, bob, carol, gw);
+ env.trust(USD(10000), alice, bob, carol);
+
+ env(pay(gw, bob, USD(100)));
+ env(pay(gw, alice, USD(100)));
+
+ env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
+ env(offer(bob, USD(100), XRP(100)), txflags(tfPassive));
+
+ auto const expectedResult = [&] {
+ if (hasFeature(featureFlow, fs) &&
+ !hasFeature(featureToStrandV2, fs))
+ return tesSUCCESS;
+ return temBAD_PATH_LOOP;
+ }();
+ // payment path: USD -> USD/XRP -> XRP/USD
+ env(pay(alice, carol, USD(100)),
+ sendmax(USD(100)),
+ path(~XRP, ~USD),
+ txflags(tfNoRippleDirect),
+ ter(expectedResult));
+ }
+ {
+ Env env(*this, features(fs));
+
+ env.fund(XRP(10000), alice, bob, carol, gw);
+ env.trust(USD(10000), alice, bob, carol);
+ env.trust(EUR(10000), alice, bob, carol);
+ env.trust(CNY(10000), alice, bob, carol);
+
+ env(pay(gw, bob, USD(100)));
+ env(pay(gw, bob, EUR(100)));
+ env(pay(gw, bob, CNY(100)));
+
+ env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
+ env(offer(bob, USD(100), EUR(100)), txflags(tfPassive));
+ env(offer(bob, EUR(100), CNY(100)), txflags(tfPassive));
+
+ // payment path: XRP->XRP/USD->USD/EUR->USD/CNY
+ env(pay(alice, carol, CNY(100)),
+ sendmax(XRP(100)),
+ path(~USD, ~EUR, ~USD, ~CNY),
+ txflags(tfNoRippleDirect),
+ ter(temBAD_PATH_LOOP));
+ }
+ }
+
+ void
+ run() override
+ {
+ testAllPairs();
+ testToStrand({featureFlow});
+ testToStrand({featureFlow, featureToStrandV2});
+ testRIPD1373({});
+ testRIPD1373({featureFlow, featureToStrandV2});
+ testLoop({});
+ testLoop({featureFlow});
+ testLoop({featureFlow, featureToStrandV2});
+ }
+};
+
+BEAST_DEFINE_TESTSUITE_MANUAL(PayStrand, app, ripple);
+
+} // test
+} // ripple
diff --git a/src/test/app/SetAuth_test.cpp b/src/test/app/SetAuth_test.cpp
index 0eb67d1a8a..a669731550 100644
--- a/src/test/app/SetAuth_test.cpp
+++ b/src/test/app/SetAuth_test.cpp
@@ -46,13 +46,13 @@ struct SetAuth_test : public beast::unit_test::suite
return jv;
}
- void testAuth()
+ void testAuth(std::initializer_list fs)
{
using namespace jtx;
auto const gw = Account("gw");
auto const USD = gw["USD"];
{
- Env env(*this);
+ Env env(*this, features(fs));
env.fund(XRP(100000), "alice", gw);
env(fset(gw, asfRequireAuth));
env(auth(gw, "alice", "USD"), ter(tecNO_LINE_REDUNDANT));
@@ -75,7 +75,9 @@ struct SetAuth_test : public beast::unit_test::suite
void run() override
{
- testAuth();
+ testAuth({});
+ testAuth({featureFlow});
+ testAuth({featureFlow, featureToStrandV2});
}
};
diff --git a/src/test/app/TrustAndBalance_test.cpp b/src/test/app/TrustAndBalance_test.cpp
index 7d10715da7..11ea0d659f 100644
--- a/src/test/app/TrustAndBalance_test.cpp
+++ b/src/test/app/TrustAndBalance_test.cpp
@@ -20,6 +20,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -45,12 +46,12 @@ class TrustAndBalance_test : public beast::unit_test::suite
};
void
- testPayNonexistent ()
+ testPayNonexistent (std::initializer_list fs)
{
testcase ("Payment to Nonexistent Account");
using namespace test::jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
env (pay (env.master, "alice", XRP(1)), ter(tecNO_DST_INSUF_XRP));
env.close();
}
@@ -161,12 +162,12 @@ class TrustAndBalance_test : public beast::unit_test::suite
}
void
- testDirectRipple ()
+ testDirectRipple (std::initializer_list fs)
{
testcase ("Direct Payment, Ripple");
using namespace test::jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
Account alice {"alice"};
Account bob {"bob"};
@@ -202,14 +203,14 @@ class TrustAndBalance_test : public beast::unit_test::suite
}
void
- testWithTransferFee (bool subscribe, bool with_rate)
+ testWithTransferFee (bool subscribe, bool with_rate, std::initializer_list fs)
{
testcase(std::string("Direct Payment: ") +
(with_rate ? "With " : "Without ") + " Xfer Fee, " +
(subscribe ? "With " : "Without ") + " Subscribe");
using namespace test::jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
auto wsc = test::makeWSClient(env.app().config());
Account gw {"gateway"};
Account alice {"alice"};
@@ -282,12 +283,12 @@ class TrustAndBalance_test : public beast::unit_test::suite
}
void
- testWithPath ()
+ testWithPath (std::initializer_list fs)
{
testcase ("Payments With Paths and Fees");
using namespace test::jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
Account gw {"gateway"};
Account alice {"alice"};
Account bob {"bob"};
@@ -330,12 +331,12 @@ class TrustAndBalance_test : public beast::unit_test::suite
}
void
- testIndirect ()
+ testIndirect (std::initializer_list fs)
{
testcase ("Indirect Payment");
using namespace test::jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
Account gw {"gateway"};
Account alice {"alice"};
Account bob {"bob"};
@@ -371,13 +372,13 @@ class TrustAndBalance_test : public beast::unit_test::suite
}
void
- testIndirectMultiPath (bool with_rate)
+ testIndirectMultiPath (bool with_rate, std::initializer_list fs)
{
testcase (std::string("Indirect Payment, Multi Path, ") +
(with_rate ? "With " : "Without ") + " Xfer Fee, ");
using namespace test::jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
Account gw {"gateway"};
Account amazon {"amazon"};
Account alice {"alice"};
@@ -437,12 +438,12 @@ class TrustAndBalance_test : public beast::unit_test::suite
}
void
- testInvoiceID ()
+ testInvoiceID (std::initializer_list fs)
{
testcase ("Set Invoice ID on Payment");
using namespace test::jtx;
- Env env {*this};
+ Env env {*this, features(fs)};
Account alice {"alice"};
auto wsc = test::makeWSClient(env.app().config());
@@ -489,19 +490,25 @@ class TrustAndBalance_test : public beast::unit_test::suite
public:
void run ()
{
- testPayNonexistent ();
testTrustNonexistent ();
testCreditLimit ();
- testDirectRipple ();
- testWithTransferFee (false, false);
- testWithTransferFee (false, true);
- testWithTransferFee (true, false);
- testWithTransferFee (true, true);
- testWithPath ();
- testIndirect ();
- testIndirectMultiPath (true);
- testIndirectMultiPath (false);
- testInvoiceID ();
+
+ auto testWithFeatures = [this](std::initializer_list fs) {
+ testPayNonexistent(fs);
+ testDirectRipple(fs);
+ testWithTransferFee(false, false, fs);
+ testWithTransferFee(false, true, fs);
+ testWithTransferFee(true, false, fs);
+ testWithTransferFee(true, true, fs);
+ testWithPath(fs);
+ testIndirect(fs);
+ testIndirectMultiPath(true, fs);
+ testIndirectMultiPath(false, fs);
+ testInvoiceID(fs);
+ };
+ testWithFeatures({});
+ testWithFeatures({featureFlow});
+ testWithFeatures({featureFlow, featureToStrandV2});
}
};
diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h
index cad23d1e7c..f7db2bbaf5 100644
--- a/src/test/jtx/Env.h
+++ b/src/test/jtx/Env.h
@@ -73,6 +73,14 @@ features (uint256 const& key, Args const&... args)
return {{key, args...}};
}
+/** Activate features in the Env ctor */
+inline
+auto
+features (std::initializer_list keys)
+{
+ return keys;
+}
+
//------------------------------------------------------------------------------
/** A transaction testing environment. */
@@ -125,6 +133,14 @@ private:
app().config().features.insert(key);
}
+ void
+ construct_arg (
+ std::initializer_list list)
+ {
+ for(auto const& key : list)
+ app().config().features.insert(key);
+ }
+
public:
Env() = delete;
Env (Env const&) = delete;
diff --git a/src/test/jtx/PathSet.h b/src/test/jtx/PathSet.h
index c2ab7f2578..d879487f7e 100644
--- a/src/test/jtx/PathSet.h
+++ b/src/test/jtx/PathSet.h
@@ -66,6 +66,7 @@ public:
}
Path& push_back (Issue const& iss);
Path& push_back (jtx::Account const& acc);
+ Path& push_back (STPathElement const& pe);
Json::Value json () const;
private:
Path& addHelper (){return *this;};
@@ -73,6 +74,12 @@ public:
Path& addHelper (First&& first, Rest&&... rest);
};
+inline Path& Path::push_back (STPathElement const& pe)
+{
+ path.emplace_back (pe);
+ return *this;
+}
+
inline Path& Path::push_back (Issue const& iss)
{
path.emplace_back (STPathElement::typeCurrency | STPathElement::typeIssuer,
diff --git a/src/test/ledger/BookDirs_test.cpp b/src/test/ledger/BookDirs_test.cpp
index e841401a91..add4a03da2 100644
--- a/src/test/ledger/BookDirs_test.cpp
+++ b/src/test/ledger/BookDirs_test.cpp
@@ -18,16 +18,17 @@
#include
#include
#include
+#include
namespace ripple {
namespace test {
struct BookDirs_test : public beast::unit_test::suite
{
- void test_bookdir()
+ void test_bookdir(std::initializer_list fs)
{
using namespace jtx;
- Env env(*this);
+ Env env(*this, features(fs));
auto gw = Account("gw");
auto USD = gw["USD"];
env.fund(XRP(1000000), "alice", "bob", "gw");
@@ -93,7 +94,8 @@ struct BookDirs_test : public beast::unit_test::suite
void run() override
{
- test_bookdir();
+ test_bookdir({});
+ test_bookdir({featureFlow, featureToStrandV2});
}
};
diff --git a/src/test/ledger/PaymentSandbox_test.cpp b/src/test/ledger/PaymentSandbox_test.cpp
index 35c3d383bf..52146a73dc 100644
--- a/src/test/ledger/PaymentSandbox_test.cpp
+++ b/src/test/ledger/PaymentSandbox_test.cpp
@@ -23,6 +23,7 @@
#include
#include
#include
+#include
namespace ripple {
namespace test {
@@ -54,12 +55,12 @@ class PaymentSandbox_test : public beast::unit_test::suite
2) New code: Path is dry because sender does not have any
GW1 to spend until the end of the transaction.
*/
- void testSelfFunding ()
+ void testSelfFunding (std::initializer_list fs)
{
testcase ("selfFunding");
using namespace jtx;
- Env env (*this);
+ Env env (*this, features(fs));
Account const gw1 ("gw1");
Account const gw2 ("gw2");
Account const snd ("snd");
@@ -95,12 +96,12 @@ class PaymentSandbox_test : public beast::unit_test::suite
env.require (balance ("rcv", USD_gw2 (2)));
}
- void testSubtractCredits ()
+ void testSubtractCredits (std::initializer_list fs)
{
testcase ("subtractCredits");
using namespace jtx;
- Env env (*this);
+ Env env (*this, features(fs));
Account const gw1 ("gw1");
Account const gw2 ("gw2");
Account const alice ("alice");
@@ -255,7 +256,7 @@ class PaymentSandbox_test : public beast::unit_test::suite
}
}
- void testTinyBalance ()
+ void testTinyBalance (std::initializer_list fs)
{
testcase ("Tiny balance");
@@ -265,7 +266,7 @@ class PaymentSandbox_test : public beast::unit_test::suite
using namespace jtx;
- Env env (*this);
+ Env env (*this, features(fs));
Account const gw ("gw");
Account const alice ("alice");
@@ -291,7 +292,8 @@ class PaymentSandbox_test : public beast::unit_test::suite
BEAST_EXPECT(pv.balanceHook (alice, gw, hugeAmt) != tinyAmt);
}
}
- void testReserve()
+
+ void testReserve(std::initializer_list fs)
{
testcase ("Reserve");
using namespace jtx;
@@ -310,7 +312,7 @@ class PaymentSandbox_test : public beast::unit_test::suite
return env.current ()->fees ().accountReserve (count);
};
- Env env (*this);
+ Env env (*this, features(fs));
Account const alice ("alice");
env.fund (reserve(env, 1), alice);
@@ -335,10 +337,14 @@ class PaymentSandbox_test : public beast::unit_test::suite
public:
void run ()
{
- testSelfFunding ();
- testSubtractCredits ();
- testTinyBalance ();
- testReserve();
+ auto testAll = [this](std::initializer_list fs) {
+ testSelfFunding(fs);
+ testSubtractCredits(fs);
+ testTinyBalance(fs);
+ testReserve(fs);
+ };
+ testAll({});
+ testAll({featureFlow, featureToStrandV2});
}
};
diff --git a/src/test/rpc/GatewayBalances_test.cpp b/src/test/rpc/GatewayBalances_test.cpp
index 8931715bcf..7eabddf1f3 100644
--- a/src/test/rpc/GatewayBalances_test.cpp
+++ b/src/test/rpc/GatewayBalances_test.cpp
@@ -16,6 +16,7 @@
//==============================================================================
#include
+#include
#include
#include
#include
@@ -29,11 +30,11 @@ class GatewayBalances_test : public beast::unit_test::suite
public:
void
- testGWB()
+ testGWB(std::initializer_list fs)
{
using namespace std::chrono_literals;
using namespace jtx;
- Env env(*this);
+ Env env(*this, features(fs));
// Gateway account and assets
Account const alice {"alice"};
@@ -152,7 +153,8 @@ public:
void
run() override
{
- testGWB();
+ testGWB({});
+ testGWB({featureFlow, featureToStrandV2});
}
};
diff --git a/src/test/rpc/NoRipple_test.cpp b/src/test/rpc/NoRipple_test.cpp
index afabd778eb..34a351ee35 100644
--- a/src/test/rpc/NoRipple_test.cpp
+++ b/src/test/rpc/NoRipple_test.cpp
@@ -17,6 +17,7 @@
*/
//==============================================================================
+#include
#include
#include
@@ -66,13 +67,12 @@ public:
}
}
- void
- testNegativeBalance()
+ void testNegativeBalance(std::initializer_list fs)
{
testcase("Set noripple on a line with negative balance");
using namespace jtx;
- Env env(*this);
+ Env env(*this, features(fs));
auto const gw = Account("gateway");
auto const alice = Account("alice");
@@ -113,13 +113,12 @@ public:
BEAST_EXPECT(!lines[0u].isMember(jss::no_ripple));
}
- void
- testPairwise()
+ void testPairwise(std::initializer_list fs)
{
testcase("pairwise NoRipple");
using namespace jtx;
- Env env(*this);
+ Env env(*this, features(fs));
auto const alice = Account("alice");
auto const bob = Account("bob");
@@ -151,13 +150,12 @@ public:
env(pay(alice, carol, bob["USD"](50)), ter(tecPATH_DRY));
}
- void
- testDefaultRipple()
+ void testDefaultRipple(std::initializer_list fs)
{
testcase("Set default ripple on an account and check new trustlines");
using namespace jtx;
- Env env(*this);
+ Env env(*this, features(fs));
auto const gw = Account("gateway");
auto const alice = Account("alice");
@@ -213,9 +211,15 @@ public:
void run ()
{
testSetAndClear();
- testNegativeBalance();
- testPairwise();
- testDefaultRipple();
+
+ auto withFeatsTests = [this](std::initializer_list fs) {
+ testNegativeBalance(fs);
+ testPairwise(fs);
+ testDefaultRipple(fs);
+ };
+ withFeatsTests({});
+ withFeatsTests({featureFlow});
+ withFeatsTests({featureFlow, featureToStrandV2});
}
};
diff --git a/src/test/unity/app_test_unity.cpp b/src/test/unity/app_test_unity.cpp
index 8b12846ff2..72c9d4b0c3 100644
--- a/src/test/unity/app_test_unity.cpp
+++ b/src/test/unity/app_test_unity.cpp
@@ -35,6 +35,7 @@
#include
#include
#include
+#include
#include
#include
#include