mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-20 18:45:55 +00:00
Rm some offers where the quality is reduced:
Substantial reductions in an offer's effective quality from its initial quality may clog offer books.
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include <ripple/app/tx/impl/OfferStream.h>
|
#include <ripple/app/tx/impl/OfferStream.h>
|
||||||
#include <ripple/basics/Log.h>
|
#include <ripple/basics/Log.h>
|
||||||
|
#include <ripple/protocol/Feature.h>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
@@ -132,6 +133,73 @@ accountFundsHelper(
|
|||||||
view, id, issue.currency, issue.account, freezeHandling, j));
|
view, id, issue.currency, issue.account, freezeHandling, j));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class TIn, class TOut>
|
||||||
|
template <class TTakerPays, class TTakerGets>
|
||||||
|
bool
|
||||||
|
TOfferStreamBase<TIn, TOut>::shouldRmSmallIncreasedQOffer() const
|
||||||
|
{
|
||||||
|
static_assert(
|
||||||
|
std::is_same_v<TTakerPays, IOUAmount> ||
|
||||||
|
std::is_same_v<TTakerPays, XRPAmount>,
|
||||||
|
"STAmount is not supported");
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
std::is_same_v<TTakerGets, IOUAmount> ||
|
||||||
|
std::is_same_v<TTakerGets, XRPAmount>,
|
||||||
|
"STAmount is not supported");
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
!std::is_same_v<TTakerPays, XRPAmount> ||
|
||||||
|
!std::is_same_v<TTakerGets, XRPAmount>,
|
||||||
|
"Cannot have XRP/XRP offers");
|
||||||
|
|
||||||
|
if (!view_.rules().enabled(fixRmSmallIncreasedQOffers))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Consider removing the offer if:
|
||||||
|
// o `TakerPays` is XRP (because of XRP drops granularity) or
|
||||||
|
// o `TakerPays` and `TakerGets` are both IOU and `TakerPays`<`TakerGets`
|
||||||
|
constexpr bool const inIsXRP = std::is_same_v<TTakerPays, XRPAmount>;
|
||||||
|
constexpr bool const outIsXRP = std::is_same_v<TTakerGets, XRPAmount>;
|
||||||
|
|
||||||
|
if constexpr (outIsXRP)
|
||||||
|
{
|
||||||
|
// If `TakerGets` is XRP, the worst this offer's quality can change is
|
||||||
|
// to about 10^-81 `TakerPays` and 1 drop `TakerGets`. This will be
|
||||||
|
// remarkably good quality for any realistic asset, so these offers
|
||||||
|
// don't need this extra check.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TAmounts<TTakerPays, TTakerGets> const ofrAmts{
|
||||||
|
toAmount<TTakerPays>(offer_.amount().in),
|
||||||
|
toAmount<TTakerGets>(offer_.amount().out)};
|
||||||
|
|
||||||
|
if constexpr (!inIsXRP && !outIsXRP)
|
||||||
|
{
|
||||||
|
if (ofrAmts.in >= ofrAmts.out)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TTakerGets const ownerFunds = toAmount<TTakerGets>(*ownerFunds_);
|
||||||
|
|
||||||
|
auto const effectiveAmounts = [&] {
|
||||||
|
if (offer_.owner() != offer_.issueOut().account &&
|
||||||
|
ownerFunds < ofrAmts.out)
|
||||||
|
{
|
||||||
|
// adjust the amounts by owner funds
|
||||||
|
return offer_.quality().ceil_out(ofrAmts, ownerFunds);
|
||||||
|
}
|
||||||
|
return ofrAmts;
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (effectiveAmounts.in > TTakerPays::minPositiveAmount())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Quality const effectiveQuality{effectiveAmounts};
|
||||||
|
return effectiveQuality < offer_.quality();
|
||||||
|
}
|
||||||
|
|
||||||
template <class TIn, class TOut>
|
template <class TIn, class TOut>
|
||||||
bool
|
bool
|
||||||
TOfferStreamBase<TIn, TOut>::step()
|
TOfferStreamBase<TIn, TOut>::step()
|
||||||
@@ -222,6 +290,68 @@ TOfferStreamBase<TIn, TOut>::step()
|
|||||||
<< "Removing became unfunded offer " << entry->key();
|
<< "Removing became unfunded offer " << entry->key();
|
||||||
}
|
}
|
||||||
offer_ = TOffer<TIn, TOut>{};
|
offer_ = TOffer<TIn, TOut>{};
|
||||||
|
// See comment at top of loop for how the offer is removed
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool const rmSmallIncreasedQOffer = [&] {
|
||||||
|
bool const inIsXRP = isXRP(offer_.issueIn());
|
||||||
|
bool const outIsXRP = isXRP(offer_.issueOut());
|
||||||
|
if (inIsXRP && !outIsXRP)
|
||||||
|
{
|
||||||
|
// Without the `if constexpr`, the
|
||||||
|
// `shouldRmSmallIncreasedQOffer` template will be instantiated
|
||||||
|
// even if it is never used. This can cause compiler errors in
|
||||||
|
// some cases, hence the `if constexpr` guard.
|
||||||
|
// Note that TIn can be XRPAmount or STAmount, and TOut can be
|
||||||
|
// IOUAmount or STAmount.
|
||||||
|
if constexpr (!(std::is_same_v<TIn, IOUAmount> ||
|
||||||
|
std::is_same_v<TOut, XRPAmount>))
|
||||||
|
return shouldRmSmallIncreasedQOffer<XRPAmount, IOUAmount>();
|
||||||
|
}
|
||||||
|
if (!inIsXRP && outIsXRP)
|
||||||
|
{
|
||||||
|
// See comment above for `if constexpr` rationale
|
||||||
|
if constexpr (!(std::is_same_v<TIn, XRPAmount> ||
|
||||||
|
std::is_same_v<TOut, IOUAmount>))
|
||||||
|
return shouldRmSmallIncreasedQOffer<IOUAmount, XRPAmount>();
|
||||||
|
}
|
||||||
|
if (!inIsXRP && !outIsXRP)
|
||||||
|
{
|
||||||
|
// See comment above for `if constexpr` rationale
|
||||||
|
if constexpr (!(std::is_same_v<TIn, XRPAmount> ||
|
||||||
|
std::is_same_v<TOut, XRPAmount>))
|
||||||
|
return shouldRmSmallIncreasedQOffer<IOUAmount, IOUAmount>();
|
||||||
|
}
|
||||||
|
assert(0); // xrp/xrp offer!?! should never happen
|
||||||
|
return false;
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (rmSmallIncreasedQOffer)
|
||||||
|
{
|
||||||
|
auto const original_funds = accountFundsHelper(
|
||||||
|
cancelView_,
|
||||||
|
offer_.owner(),
|
||||||
|
amount.out,
|
||||||
|
offer_.issueOut(),
|
||||||
|
fhZERO_IF_FROZEN,
|
||||||
|
j_);
|
||||||
|
|
||||||
|
if (original_funds == *ownerFunds_)
|
||||||
|
{
|
||||||
|
permRmOffer(entry->key());
|
||||||
|
JLOG(j_.trace())
|
||||||
|
<< "Removing tiny offer due to reduced quality "
|
||||||
|
<< entry->key();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
JLOG(j_.trace()) << "Removing tiny offer that became tiny due "
|
||||||
|
"to reduced quality "
|
||||||
|
<< entry->key();
|
||||||
|
}
|
||||||
|
offer_ = TOffer<TIn, TOut>{};
|
||||||
|
// See comment at top of loop for how the offer is removed
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ protected:
|
|||||||
virtual void
|
virtual void
|
||||||
permRmOffer(uint256 const& offerIndex) = 0;
|
permRmOffer(uint256 const& offerIndex) = 0;
|
||||||
|
|
||||||
|
template <class TTakerPays, class TTakerGets>
|
||||||
|
bool
|
||||||
|
shouldRmSmallIncreasedQOffer() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TOfferStreamBase(
|
TOfferStreamBase(
|
||||||
ApplyView& view,
|
ApplyView& view,
|
||||||
|
|||||||
@@ -129,6 +129,9 @@ public:
|
|||||||
{
|
{
|
||||||
return mantissa_;
|
return mantissa_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static IOUAmount
|
||||||
|
minPositiveAmount();
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
|
|||||||
@@ -238,6 +238,12 @@ public:
|
|||||||
s >> val.drops_;
|
s >> val.drops_;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static XRPAmount
|
||||||
|
minPositiveAmount()
|
||||||
|
{
|
||||||
|
return XRPAmount{1};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Number of drops per 1 XRP */
|
/** Number of drops per 1 XRP */
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ static std::int64_t const maxMantissa = 9999999999999999ull;
|
|||||||
static int const minExponent = -96;
|
static int const minExponent = -96;
|
||||||
static int const maxExponent = 80;
|
static int const maxExponent = 80;
|
||||||
|
|
||||||
|
IOUAmount
|
||||||
|
IOUAmount::minPositiveAmount()
|
||||||
|
{
|
||||||
|
return IOUAmount(minMantissa, minExponent);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
IOUAmount::normalize()
|
IOUAmount::normalize()
|
||||||
{
|
{
|
||||||
@@ -353,7 +359,7 @@ mulRatio(
|
|||||||
{
|
{
|
||||||
if (!result)
|
if (!result)
|
||||||
{
|
{
|
||||||
return IOUAmount(minMantissa, minExponent);
|
return IOUAmount::minPositiveAmount();
|
||||||
}
|
}
|
||||||
// This addition cannot overflow because the mantissa is already
|
// This addition cannot overflow because the mantissa is already
|
||||||
// normalized
|
// normalized
|
||||||
|
|||||||
@@ -63,11 +63,7 @@ toSTAmount(XRPAmount const& xrp, Issue const& iss)
|
|||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
T
|
T
|
||||||
toAmount(STAmount const& amt)
|
toAmount(STAmount const& amt) = delete;
|
||||||
{
|
|
||||||
static_assert(sizeof(T) == -1, "Must use specialized function");
|
|
||||||
return T(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline STAmount
|
inline STAmount
|
||||||
@@ -102,6 +98,28 @@ toAmount<XRPAmount>(STAmount const& amt)
|
|||||||
return XRPAmount(sMant);
|
return XRPAmount(sMant);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T
|
||||||
|
toAmount(IOUAmount const& amt) = delete;
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline IOUAmount
|
||||||
|
toAmount<IOUAmount>(IOUAmount const& amt)
|
||||||
|
{
|
||||||
|
return amt;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T
|
||||||
|
toAmount(XRPAmount const& amt) = delete;
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline XRPAmount
|
||||||
|
toAmount<XRPAmount>(XRPAmount const& amt)
|
||||||
|
{
|
||||||
|
return amt;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ class FeatureCollections
|
|||||||
"TicketBatch",
|
"TicketBatch",
|
||||||
"FlowSortStrands",
|
"FlowSortStrands",
|
||||||
"fixSTAmountCanonicalize",
|
"fixSTAmountCanonicalize",
|
||||||
|
"fixRmSmallIncreasedQOffers",
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<uint256> features;
|
std::vector<uint256> features;
|
||||||
@@ -376,6 +377,7 @@ extern uint256 const featureNegativeUNL;
|
|||||||
extern uint256 const featureTicketBatch;
|
extern uint256 const featureTicketBatch;
|
||||||
extern uint256 const featureFlowSortStrands;
|
extern uint256 const featureFlowSortStrands;
|
||||||
extern uint256 const fixSTAmountCanonicalize;
|
extern uint256 const fixSTAmountCanonicalize;
|
||||||
|
extern uint256 const fixRmSmallIncreasedQOffers;
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|
||||||
|
|||||||
@@ -132,6 +132,13 @@ public:
|
|||||||
/** Create a quality from the ratio of two amounts. */
|
/** Create a quality from the ratio of two amounts. */
|
||||||
explicit Quality(Amounts const& amount);
|
explicit Quality(Amounts const& amount);
|
||||||
|
|
||||||
|
/** Create a quality from the ratio of two amounts. */
|
||||||
|
template <class In, class Out>
|
||||||
|
Quality(TAmounts<In, Out> const& amount)
|
||||||
|
: Quality(Amounts(toSTAmount(amount.in), toSTAmount(amount.out)))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/** Create a quality from the ratio of two amounts. */
|
/** Create a quality from the ratio of two amounts. */
|
||||||
template <class In, class Out>
|
template <class In, class Out>
|
||||||
Quality(Out const& out, In const& in)
|
Quality(Out const& out, In const& in)
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ detail::supportedAmendments()
|
|||||||
"TicketBatch",
|
"TicketBatch",
|
||||||
"FlowSortStrands",
|
"FlowSortStrands",
|
||||||
"fixSTAmountCanonicalize",
|
"fixSTAmountCanonicalize",
|
||||||
|
"fixRmSmallIncreasedQOffers",
|
||||||
};
|
};
|
||||||
return supported;
|
return supported;
|
||||||
}
|
}
|
||||||
@@ -190,7 +191,8 @@ uint256 const
|
|||||||
featureNegativeUNL = *getRegisteredFeature("NegativeUNL"),
|
featureNegativeUNL = *getRegisteredFeature("NegativeUNL"),
|
||||||
featureTicketBatch = *getRegisteredFeature("TicketBatch"),
|
featureTicketBatch = *getRegisteredFeature("TicketBatch"),
|
||||||
featureFlowSortStrands = *getRegisteredFeature("FlowSortStrands"),
|
featureFlowSortStrands = *getRegisteredFeature("FlowSortStrands"),
|
||||||
fixSTAmountCanonicalize = *getRegisteredFeature("fixSTAmountCanonicalize");
|
fixSTAmountCanonicalize = *getRegisteredFeature("fixSTAmountCanonicalize"),
|
||||||
|
fixRmSmallIncreasedQOffers = *getRegisteredFeature("fixRmSmallIncreasedQOffers");
|
||||||
|
|
||||||
// The following amendments have been active for at least two years. Their
|
// The following amendments have been active for at least two years. Their
|
||||||
// pre-amendment code has been removed and the identifiers are deprecated.
|
// pre-amendment code has been removed and the identifiers are deprecated.
|
||||||
|
|||||||
@@ -320,17 +320,20 @@ public:
|
|||||||
Env env{*this, features};
|
Env env{*this, features};
|
||||||
|
|
||||||
env.fund(XRP(10000), alice, bob, carol, dan, erin, gw);
|
env.fund(XRP(10000), alice, bob, carol, dan, erin, gw);
|
||||||
|
env.close();
|
||||||
env.trust(USD(1000), alice, bob, carol, dan, erin);
|
env.trust(USD(1000), alice, bob, carol, dan, erin);
|
||||||
|
env.close();
|
||||||
env(pay(gw, carol, USD(0.99999)));
|
env(pay(gw, carol, USD(0.99999)));
|
||||||
env(pay(gw, dan, USD(1)));
|
env(pay(gw, dan, USD(1)));
|
||||||
env(pay(gw, erin, USD(1)));
|
env(pay(gw, erin, USD(1)));
|
||||||
|
env.close();
|
||||||
|
|
||||||
// Carol doesn't quite have enough funds for this offer
|
// Carol doesn't quite have enough funds for this offer
|
||||||
// The amount left after this offer is taken will cause
|
// The amount left after this offer is taken will cause
|
||||||
// STAmount to incorrectly round to zero when the next offer
|
// STAmount to incorrectly round to zero when the next offer
|
||||||
// (at a good quality) is considered. (when the
|
// (at a good quality) is considered. (when the now removed
|
||||||
// stAmountCalcSwitchover2 patch is inactive)
|
// stAmountCalcSwitchover2 patch was inactive)
|
||||||
env(offer(carol, drops(1), USD(1)));
|
env(offer(carol, drops(1), USD(0.99999)));
|
||||||
// Offer at a quality poor enough so when the input xrp is
|
// Offer at a quality poor enough so when the input xrp is
|
||||||
// calculated in the reverse pass, the amount is not zero.
|
// calculated in the reverse pass, the amount is not zero.
|
||||||
env(offer(dan, XRP(100), USD(1)));
|
env(offer(dan, XRP(100), USD(1)));
|
||||||
@@ -340,9 +343,9 @@ public:
|
|||||||
// It is considered after the offer from carol, which leaves a
|
// It is considered after the offer from carol, which leaves a
|
||||||
// tiny amount left to pay. When calculating the amount of xrp
|
// tiny amount left to pay. When calculating the amount of xrp
|
||||||
// needed for this offer, it will incorrectly compute zero in both
|
// needed for this offer, it will incorrectly compute zero in both
|
||||||
// the forward and reverse passes (when the stAmountCalcSwitchover2
|
// the forward and reverse passes (when the now removed
|
||||||
// is inactive.)
|
// stAmountCalcSwitchover2 was inactive.)
|
||||||
env(offer(erin, drops(2), USD(2)));
|
env(offer(erin, drops(2), USD(1)));
|
||||||
|
|
||||||
env(pay(alice, bob, USD(1)),
|
env(pay(alice, bob, USD(1)),
|
||||||
path(~USD),
|
path(~USD),
|
||||||
@@ -356,6 +359,317 @@ public:
|
|||||||
env.require(balance(erin, USD(0.99999)), offers(erin, 1));
|
env.require(balance(erin, USD(0.99999)), offers(erin, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testRmSmallIncreasedQOffersXRP(FeatureBitset features)
|
||||||
|
{
|
||||||
|
testcase("Rm small increased q offers XRP");
|
||||||
|
|
||||||
|
// Carol places an offer, but cannot fully fund the offer. When her
|
||||||
|
// funding is taken into account, the offer's quality drops below its
|
||||||
|
// initial quality and has an input amount of 1 drop. This is removed as
|
||||||
|
// an offer that may block offer books.
|
||||||
|
|
||||||
|
using namespace jtx;
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
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"];
|
||||||
|
|
||||||
|
// Test offer crossing
|
||||||
|
for (auto crossBothOffers : {false, true})
|
||||||
|
{
|
||||||
|
Env env{*this, features};
|
||||||
|
|
||||||
|
env.fund(XRP(10000), alice, bob, carol, gw);
|
||||||
|
env.close();
|
||||||
|
env.trust(USD(1000), alice, bob, carol);
|
||||||
|
// underfund carol's offer
|
||||||
|
auto initialCarolUSD = USD(0.499);
|
||||||
|
env(pay(gw, carol, initialCarolUSD));
|
||||||
|
env(pay(gw, bob, USD(100)));
|
||||||
|
env.close();
|
||||||
|
// This offer is underfunded
|
||||||
|
env(offer(carol, drops(1), USD(1)));
|
||||||
|
env.close();
|
||||||
|
// offer at a lower quality
|
||||||
|
env(offer(bob, drops(2), USD(1), tfPassive));
|
||||||
|
env.close();
|
||||||
|
env.require(offers(bob, 1), offers(carol, 1));
|
||||||
|
|
||||||
|
// alice places an offer that crosses carol's; depending on
|
||||||
|
// "crossBothOffers" it may cross bob's as well
|
||||||
|
auto aliceTakerGets = crossBothOffers ? drops(2) : drops(1);
|
||||||
|
env(offer(alice, USD(1), aliceTakerGets));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
if (features[fixRmSmallIncreasedQOffers])
|
||||||
|
{
|
||||||
|
env.require(
|
||||||
|
offers(carol, 0),
|
||||||
|
balance(
|
||||||
|
carol,
|
||||||
|
initialCarolUSD)); // offer is removed but not taken
|
||||||
|
if (crossBothOffers)
|
||||||
|
{
|
||||||
|
env.require(
|
||||||
|
offers(alice, 0),
|
||||||
|
balance(alice, USD(1))); // alice's offer is crossed
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
env.require(
|
||||||
|
offers(alice, 1),
|
||||||
|
balance(
|
||||||
|
alice, USD(0))); // alice's offer is not crossed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
env.require(
|
||||||
|
offers(alice, 1),
|
||||||
|
offers(bob, 1),
|
||||||
|
offers(carol, 1),
|
||||||
|
balance(alice, USD(0)),
|
||||||
|
balance(
|
||||||
|
carol,
|
||||||
|
initialCarolUSD)); // offer is not crossed at all
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test payments
|
||||||
|
for (auto partialPayment : {false, true})
|
||||||
|
{
|
||||||
|
Env env{*this, features};
|
||||||
|
|
||||||
|
env.fund(XRP(10000), alice, bob, carol, gw);
|
||||||
|
env.close();
|
||||||
|
env.trust(USD(1000), alice, bob, carol);
|
||||||
|
env.close();
|
||||||
|
auto const initialCarolUSD = USD(0.999);
|
||||||
|
env(pay(gw, carol, initialCarolUSD));
|
||||||
|
env.close();
|
||||||
|
env(pay(gw, bob, USD(100)));
|
||||||
|
env.close();
|
||||||
|
env(offer(carol, drops(1), USD(1)));
|
||||||
|
env.close();
|
||||||
|
env(offer(bob, drops(2), USD(2), tfPassive));
|
||||||
|
env.close();
|
||||||
|
env.require(offers(bob, 1), offers(carol, 1));
|
||||||
|
|
||||||
|
std::uint32_t const flags = partialPayment
|
||||||
|
? (tfNoRippleDirect | tfPartialPayment)
|
||||||
|
: tfNoRippleDirect;
|
||||||
|
|
||||||
|
TER const expectedTer =
|
||||||
|
partialPayment ? TER{tesSUCCESS} : TER{tecPATH_PARTIAL};
|
||||||
|
|
||||||
|
env(pay(alice, bob, USD(5)),
|
||||||
|
path(~USD),
|
||||||
|
sendmax(XRP(1)),
|
||||||
|
txflags(flags),
|
||||||
|
ter(expectedTer));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
if (features[fixRmSmallIncreasedQOffers])
|
||||||
|
{
|
||||||
|
if (expectedTer == tesSUCCESS)
|
||||||
|
{
|
||||||
|
env.require(offers(carol, 0));
|
||||||
|
env.require(balance(
|
||||||
|
carol,
|
||||||
|
initialCarolUSD)); // offer is removed but not taken
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Offers are not removed when payments fail
|
||||||
|
// If that is addressed, the test should show that carol's
|
||||||
|
// offer is removed but not taken, as in the other branch of
|
||||||
|
// this if statement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (partialPayment)
|
||||||
|
{
|
||||||
|
env.require(offers(carol, 0));
|
||||||
|
env.require(
|
||||||
|
balance(carol, USD(0))); // offer is removed and taken
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// offer is not removed or taken
|
||||||
|
BEAST_EXPECT(isOffer(env, carol, drops(1), USD(1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testRmSmallIncreasedQOffersIOU(FeatureBitset features)
|
||||||
|
{
|
||||||
|
testcase("Rm small increased q offers IOU");
|
||||||
|
|
||||||
|
// Carol places an offer, but cannot fully fund the offer. When her
|
||||||
|
// funding is taken into account, the offer's quality drops below its
|
||||||
|
// initial quality and has an input amount of 1 drop. This is removed as
|
||||||
|
// an offer that may block offer books.
|
||||||
|
|
||||||
|
using namespace jtx;
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
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 tinyAmount = [&](IOU const& iou) -> PrettyAmount {
|
||||||
|
STAmount amt(
|
||||||
|
iou.issue(),
|
||||||
|
/*mantissa*/ 1,
|
||||||
|
/*exponent*/ -81);
|
||||||
|
return PrettyAmount(amt, iou.account.name());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test offer crossing
|
||||||
|
for (auto crossBothOffers : {false, true})
|
||||||
|
{
|
||||||
|
Env env{*this, features};
|
||||||
|
|
||||||
|
env.fund(XRP(10000), alice, bob, carol, gw);
|
||||||
|
env.close();
|
||||||
|
env.trust(USD(1000), alice, bob, carol);
|
||||||
|
env.trust(EUR(1000), alice, bob, carol);
|
||||||
|
// underfund carol's offer
|
||||||
|
auto initialCarolUSD = tinyAmount(USD);
|
||||||
|
env(pay(gw, carol, initialCarolUSD));
|
||||||
|
env(pay(gw, bob, USD(100)));
|
||||||
|
env(pay(gw, alice, EUR(100)));
|
||||||
|
env.close();
|
||||||
|
// This offer is underfunded
|
||||||
|
env(offer(carol, EUR(1), USD(10)));
|
||||||
|
env.close();
|
||||||
|
// offer at a lower quality
|
||||||
|
env(offer(bob, EUR(1), USD(5), tfPassive));
|
||||||
|
env.close();
|
||||||
|
env.require(offers(bob, 1), offers(carol, 1));
|
||||||
|
|
||||||
|
// alice places an offer that crosses carol's; depending on
|
||||||
|
// "crossBothOffers" it may cross bob's as well
|
||||||
|
// Whatever
|
||||||
|
auto aliceTakerGets = crossBothOffers ? EUR(0.2) : EUR(0.1);
|
||||||
|
env(offer(alice, USD(1), aliceTakerGets));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
if (features[fixRmSmallIncreasedQOffers])
|
||||||
|
{
|
||||||
|
env.require(
|
||||||
|
offers(carol, 0),
|
||||||
|
balance(
|
||||||
|
carol,
|
||||||
|
initialCarolUSD)); // offer is removed but not taken
|
||||||
|
if (crossBothOffers)
|
||||||
|
{
|
||||||
|
env.require(
|
||||||
|
offers(alice, 0),
|
||||||
|
balance(alice, USD(1))); // alice's offer is crossed
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
env.require(
|
||||||
|
offers(alice, 1),
|
||||||
|
balance(
|
||||||
|
alice, USD(0))); // alice's offer is not crossed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
env.require(
|
||||||
|
offers(alice, 1),
|
||||||
|
offers(bob, 1),
|
||||||
|
offers(carol, 1),
|
||||||
|
balance(alice, USD(0)),
|
||||||
|
balance(
|
||||||
|
carol,
|
||||||
|
initialCarolUSD)); // offer is not crossed at all
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test payments
|
||||||
|
for (auto partialPayment : {false, true})
|
||||||
|
{
|
||||||
|
Env env{*this, features};
|
||||||
|
|
||||||
|
env.fund(XRP(10000), alice, bob, carol, gw);
|
||||||
|
env.close();
|
||||||
|
env.trust(USD(1000), alice, bob, carol);
|
||||||
|
env.trust(EUR(1000), alice, bob, carol);
|
||||||
|
env.close();
|
||||||
|
// underfund carol's offer
|
||||||
|
auto const initialCarolUSD = tinyAmount(USD);
|
||||||
|
env(pay(gw, carol, initialCarolUSD));
|
||||||
|
env(pay(gw, bob, USD(100)));
|
||||||
|
env(pay(gw, alice, EUR(100)));
|
||||||
|
env.close();
|
||||||
|
// This offer is underfunded
|
||||||
|
env(offer(carol, EUR(1), USD(2)));
|
||||||
|
env.close();
|
||||||
|
env(offer(bob, EUR(2), USD(4), tfPassive));
|
||||||
|
env.close();
|
||||||
|
env.require(offers(bob, 1), offers(carol, 1));
|
||||||
|
|
||||||
|
std::uint32_t const flags = partialPayment
|
||||||
|
? (tfNoRippleDirect | tfPartialPayment)
|
||||||
|
: tfNoRippleDirect;
|
||||||
|
|
||||||
|
TER const expectedTer =
|
||||||
|
partialPayment ? TER{tesSUCCESS} : TER{tecPATH_PARTIAL};
|
||||||
|
|
||||||
|
env(pay(alice, bob, USD(5)),
|
||||||
|
path(~USD),
|
||||||
|
sendmax(EUR(10)),
|
||||||
|
txflags(flags),
|
||||||
|
ter(expectedTer));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
if (features[fixRmSmallIncreasedQOffers])
|
||||||
|
{
|
||||||
|
if (expectedTer == tesSUCCESS)
|
||||||
|
{
|
||||||
|
env.require(offers(carol, 0));
|
||||||
|
env.require(balance(
|
||||||
|
carol,
|
||||||
|
initialCarolUSD)); // offer is removed but not taken
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Offers are not removed when payments fail
|
||||||
|
// If that is addressed, the test should show that carol's
|
||||||
|
// offer is removed but not taken, as in the other branch of
|
||||||
|
// this if statement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (partialPayment)
|
||||||
|
{
|
||||||
|
env.require(offers(carol, 0));
|
||||||
|
env.require(
|
||||||
|
balance(carol, USD(0))); // offer is removed and taken
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// offer is not removed or taken
|
||||||
|
BEAST_EXPECT(isOffer(env, carol, EUR(1), USD(2)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
testEnforceNoRipple(FeatureBitset features)
|
testEnforceNoRipple(FeatureBitset features)
|
||||||
{
|
{
|
||||||
@@ -5387,7 +5701,7 @@ public:
|
|||||||
{
|
{
|
||||||
// An assert was falsely triggering when computing rates for offers.
|
// An assert was falsely triggering when computing rates for offers.
|
||||||
// This unit test would trigger that assert (which has been removed).
|
// This unit test would trigger that assert (which has been removed).
|
||||||
testcase("false assert");
|
testcase("incorrect assert fixed");
|
||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
|
|
||||||
Env env{*this};
|
Env env{*this};
|
||||||
@@ -5459,6 +5773,8 @@ public:
|
|||||||
testTickSize(features);
|
testTickSize(features);
|
||||||
testTicketOffer(features);
|
testTicketOffer(features);
|
||||||
testTicketCancelOffer(features);
|
testTicketCancelOffer(features);
|
||||||
|
testRmSmallIncreasedQOffersXRP(features);
|
||||||
|
testRmSmallIncreasedQOffersIOU(features);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -5468,10 +5784,12 @@ public:
|
|||||||
FeatureBitset const all{supported_amendments()};
|
FeatureBitset const all{supported_amendments()};
|
||||||
FeatureBitset const flowCross{featureFlowCross};
|
FeatureBitset const flowCross{featureFlowCross};
|
||||||
FeatureBitset const takerDryOffer{fixTakerDryOfferRemoval};
|
FeatureBitset const takerDryOffer{fixTakerDryOfferRemoval};
|
||||||
|
FeatureBitset const rmSmallIncreasedQOffers{fixRmSmallIncreasedQOffers};
|
||||||
|
|
||||||
testAll(all - takerDryOffer);
|
testAll(all - takerDryOffer);
|
||||||
testAll(all - flowCross - takerDryOffer);
|
testAll(all - flowCross - takerDryOffer);
|
||||||
testAll(all - flowCross);
|
testAll(all - flowCross);
|
||||||
|
testAll(all - rmSmallIncreasedQOffers);
|
||||||
testAll(all);
|
testAll(all);
|
||||||
testFalseAssert();
|
testFalseAssert();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user