mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
New rules for payment paths:
* Sanity check on newly created strands * Better loop detection * Better tests (test every combination of path element pairs) * Disallow any root issuer (even for xrp) * Disallow compount element typs in path * Issue was not reset when currency was XRP * Add amendment
This commit is contained in:
@@ -21,61 +21,16 @@
|
||||
#include <ripple/app/paths/impl/Steps.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/ledger/ApplyViewImpl.h>
|
||||
#include <ripple/ledger/PaymentSandbox.h>
|
||||
#include <ripple/ledger/Sandbox.h>
|
||||
#include <test/jtx/PathSet.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
|
||||
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<std::runtime_error> ("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<std::int64_t>::value * xrpAmount - feeDrops);
|
||||
};
|
||||
|
||||
bool equal (std::unique_ptr<Step> const& s1,
|
||||
DirectStepInfo const& dsi)
|
||||
{
|
||||
if (!s1)
|
||||
return false;
|
||||
return test::directStepEqual (*s1, dsi.src, dsi.dst, dsi.currency);
|
||||
}
|
||||
|
||||
bool equal (std::unique_ptr<Step> const& s1,
|
||||
XRPEndpointStepInfo const& xrpsi)
|
||||
{
|
||||
if (!s1)
|
||||
return false;
|
||||
return test::xrpEndpointStepEqual (*s1, xrpsi.acc);
|
||||
}
|
||||
|
||||
bool equal (std::unique_ptr<Step> const& s1, ripple::Book const& bsi)
|
||||
{
|
||||
if (!s1)
|
||||
return false;
|
||||
return bookStepEqual (*s1, bsi);
|
||||
}
|
||||
|
||||
template <class Iter>
|
||||
bool strandEqualHelper (Iter i)
|
||||
{
|
||||
// base case. all args processed and found equal.
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class Iter, class StepInfo, class... Args>
|
||||
bool strandEqualHelper (Iter i, StepInfo&& si, Args&&... args)
|
||||
{
|
||||
if (!equal (*i, std::forward<StepInfo> (si)))
|
||||
return false;
|
||||
return strandEqualHelper (++i, std::forward<Args> (args)...);
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
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> (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<uint256> 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<Issue> 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<decltype (expSteps)> (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<uint256> 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<uint256> 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<uint256> 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<uint256> 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<uint256> 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<uint256> 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<uint256> 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<uint256> 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<uint256> 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<class... Features>
|
||||
void
|
||||
testReexecuteDirectStep(Features&&... fs)
|
||||
testReexecuteDirectStep(std::initializer_list<uint256> 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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user