Skip to content

Commit ae7ea33

Browse files
authored
fixReducedOffersV2: prevent offers from blocking order books: (#5032)
Fixes issue #4937. The fixReducedOffersV1 amendment fixed certain forms of offer modification that could lead to blocked order books. Reduced offers can block order books if the effective quality of the reduced offer is worse than the quality of the original offer (from the perspective of the taker). It turns out that, for small values, the quality of the reduced offer can be significantly affected by the rounding mode used during scaling computations. Issue #4937 identified an additional code path that modified offers in a way that could lead to blocked order books. This commit changes the rounding in that newly located code path so the quality of the modified offer is never worse than the quality of the offer as it was originally placed. It is possible that additional ways of producing blocking offers will come to light. Therefore there may be a future need for a V3 amendment.
1 parent 263e984 commit ae7ea33

File tree

12 files changed

+534
-159
lines changed

12 files changed

+534
-159
lines changed

src/ripple/app/paths/AMMOffer.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -103,15 +103,15 @@ class AMMOffer
103103
limitOut(
104104
TAmounts<TIn, TOut> const& offrAmt,
105105
TOut const& limit,
106-
bool fixReducedOffers,
107106
bool roundUp) const;
108107

109108
/** Limit in of the provided offer. If one-path then swapIn
110109
* using current balances. If multi-path then ceil_in using
111110
* current quality.
112111
*/
113112
TAmounts<TIn, TOut>
114-
limitIn(TAmounts<TIn, TOut> const& offrAmt, TIn const& limit) const;
113+
limitIn(TAmounts<TIn, TOut> const& offrAmt, TIn const& limit, bool roundUp)
114+
const;
115115

116116
QualityFunction
117117
getQualityFunc() const;

src/ripple/app/paths/impl/AMMOffer.cpp

+10-3
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ TAmounts<TIn, TOut>
8181
AMMOffer<TIn, TOut>::limitOut(
8282
TAmounts<TIn, TOut> const& offrAmt,
8383
TOut const& limit,
84-
bool fixReducedOffers,
8584
bool roundUp) const
8685
{
8786
// Change the offer size proportionally to the original offer quality
@@ -92,7 +91,8 @@ AMMOffer<TIn, TOut>::limitOut(
9291
// poolPays * poolGets < (poolPays - assetOut) * (poolGets + assetIn)
9392
if (ammLiquidity_.multiPath())
9493
{
95-
if (fixReducedOffers)
94+
if (auto const& rules = getCurrentTransactionRules();
95+
rules && rules->enabled(fixReducedOffersV1))
9696
// It turns out that the ceil_out implementation has some slop in
9797
// it. ceil_out_strict removes that slop. But removing that slop
9898
// affects transaction outcomes, so the change must be made using
@@ -110,11 +110,18 @@ template <typename TIn, typename TOut>
110110
TAmounts<TIn, TOut>
111111
AMMOffer<TIn, TOut>::limitIn(
112112
TAmounts<TIn, TOut> const& offrAmt,
113-
TIn const& limit) const
113+
TIn const& limit,
114+
bool roundUp) const
114115
{
115116
// See the comments above in limitOut().
116117
if (ammLiquidity_.multiPath())
118+
{
119+
if (auto const& rules = getCurrentTransactionRules();
120+
rules && rules->enabled(fixReducedOffersV2))
121+
return quality().ceil_in_strict(offrAmt, limit, roundUp);
122+
117123
return quality().ceil_in(offrAmt, limit);
124+
}
118125
return {limit, swapAssetIn(balances_, limit, ammLiquidity_.tradingFee())};
119126
}
120127

src/ripple/app/paths/impl/BookStep.cpp

+13-12
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,14 @@ limitStepIn(
668668
stpAmt.in = limit;
669669
auto const inLmt =
670670
mulRatio(stpAmt.in, QUALITY_ONE, transferRateIn, /*roundUp*/ false);
671-
ofrAmt = offer.limitIn(ofrAmt, inLmt);
671+
// It turns out we can prevent order book blocking by (strictly)
672+
// rounding down the ceil_in() result. By rounding down we guarantee
673+
// that the quality of an offer left in the ledger is as good or
674+
// better than the quality of the containing order book page.
675+
//
676+
// This adjustment changes transaction outcomes, so it must be made
677+
// under an amendment.
678+
ofrAmt = offer.limitIn(ofrAmt, inLmt, /* roundUp */ false);
672679
stpAmt.out = ofrAmt.out;
673680
ownerGives = mulRatio(
674681
ofrAmt.out, transferRateOut, QUALITY_ONE, /*roundUp*/ false);
@@ -685,8 +692,7 @@ limitStepOut(
685692
TOut& ownerGives,
686693
std::uint32_t transferRateIn,
687694
std::uint32_t transferRateOut,
688-
TOut const& limit,
689-
Rules const& rules)
695+
TOut const& limit)
690696
{
691697
if (limit < stpAmt.out)
692698
{
@@ -696,7 +702,6 @@ limitStepOut(
696702
ofrAmt = offer.limitOut(
697703
ofrAmt,
698704
stpAmt.out,
699-
rules.enabled(fixReducedOffersV1),
700705
/*roundUp*/ true);
701706
stpAmt.in =
702707
mulRatio(ofrAmt.in, transferRateIn, QUALITY_ONE, /*roundUp*/ true);
@@ -736,7 +741,6 @@ BookStep<TIn, TOut, TDerived>::forEachOffer(
736741
sb, afView, book_, sb.parentCloseTime(), counter, j_);
737742

738743
bool const flowCross = afView.rules().enabled(featureFlowCross);
739-
bool const fixReduced = afView.rules().enabled(fixReducedOffersV1);
740744
bool offerAttempted = false;
741745
std::optional<Quality> ofrQ;
742746
auto execOffer = [&](auto& offer) {
@@ -817,8 +821,7 @@ BookStep<TIn, TOut, TDerived>::forEachOffer(
817821
// It turns out we can prevent order book blocking by (strictly)
818822
// rounding down the ceil_out() result. This adjustment changes
819823
// transaction outcomes, so it must be made under an amendment.
820-
ofrAmt = offer.limitOut(
821-
ofrAmt, stpAmt.out, fixReduced, /*roundUp*/ false);
824+
ofrAmt = offer.limitOut(ofrAmt, stpAmt.out, /*roundUp*/ false);
822825

823826
stpAmt.in =
824827
mulRatio(ofrAmt.in, ofrInRate, QUALITY_ONE, /*roundUp*/ true);
@@ -1055,8 +1058,7 @@ BookStep<TIn, TOut, TDerived>::revImp(
10551058
ownerGivesAdj,
10561059
transferRateIn,
10571060
transferRateOut,
1058-
remainingOut,
1059-
afView.rules());
1061+
remainingOut);
10601062
remainingOut = beast::zero;
10611063
savedIns.insert(stpAdjAmt.in);
10621064
savedOuts.insert(remainingOut);
@@ -1208,8 +1210,7 @@ BookStep<TIn, TOut, TDerived>::fwdImp(
12081210
ownerGivesAdjRev,
12091211
transferRateIn,
12101212
transferRateOut,
1211-
remainingOut,
1212-
afView.rules());
1213+
remainingOut);
12131214

12141215
if (stpAdjAmtRev.in == remainingIn)
12151216
{
@@ -1228,7 +1229,7 @@ BookStep<TIn, TOut, TDerived>::fwdImp(
12281229
}
12291230
else
12301231
{
1231-
// This is (likely) a problem case, and wil be caught
1232+
// This is (likely) a problem case, and will be caught
12321233
// with later checks
12331234
savedOuts.insert(lastOutAmt);
12341235
}

src/ripple/app/tx/impl/Offer.h

+16-6
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <ripple/basics/contract.h>
2424
#include <ripple/ledger/View.h>
2525
#include <ripple/protocol/Quality.h>
26+
#include <ripple/protocol/Rules.h>
2627
#include <ripple/protocol/SField.h>
2728
#include <ripple/protocol/STLedgerEntry.h>
2829
#include <ostream>
@@ -140,11 +141,11 @@ class TOffer : private TOfferBase<TIn, TOut>
140141
limitOut(
141142
TAmounts<TIn, TOut> const& offrAmt,
142143
TOut const& limit,
143-
bool fixReducedOffers,
144144
bool roundUp) const;
145145

146146
TAmounts<TIn, TOut>
147-
limitIn(TAmounts<TIn, TOut> const& offrAmt, TIn const& limit) const;
147+
limitIn(TAmounts<TIn, TOut> const& offrAmt, TIn const& limit, bool roundUp)
148+
const;
148149

149150
template <typename... Args>
150151
static TER
@@ -219,10 +220,10 @@ TAmounts<TIn, TOut>
219220
TOffer<TIn, TOut>::limitOut(
220221
TAmounts<TIn, TOut> const& offrAmt,
221222
TOut const& limit,
222-
bool fixReducedOffers,
223223
bool roundUp) const
224224
{
225-
if (fixReducedOffers)
225+
if (auto const& rules = getCurrentTransactionRules();
226+
rules && rules->enabled(fixReducedOffersV1))
226227
// It turns out that the ceil_out implementation has some slop in
227228
// it. ceil_out_strict removes that slop. But removing that slop
228229
// affects transaction outcomes, so the change must be made using
@@ -233,9 +234,18 @@ TOffer<TIn, TOut>::limitOut(
233234

234235
template <class TIn, class TOut>
235236
TAmounts<TIn, TOut>
236-
TOffer<TIn, TOut>::limitIn(TAmounts<TIn, TOut> const& offrAmt, TIn const& limit)
237-
const
237+
TOffer<TIn, TOut>::limitIn(
238+
TAmounts<TIn, TOut> const& offrAmt,
239+
TIn const& limit,
240+
bool roundUp) const
238241
{
242+
if (auto const& rules = getCurrentTransactionRules();
243+
rules && rules->enabled(fixReducedOffersV2))
244+
// It turns out that the ceil_in implementation has some slop in
245+
// it. ceil_in_strict removes that slop. But removing that slop
246+
// affects transaction outcomes, so the change must be made using
247+
// an amendment.
248+
return quality().ceil_in_strict(offrAmt, limit, roundUp);
239249
return m_quality.ceil_in(offrAmt, limit);
240250
}
241251

src/ripple/protocol/Feature.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ namespace detail {
7474
// Feature.cpp. Because it's only used to reserve storage, and determine how
7575
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
7676
// the actual number of amendments. A LogicError on startup will verify this.
77-
static constexpr std::size_t numFeatures = 73;
77+
static constexpr std::size_t numFeatures = 74;
7878

7979
/** Amendments that this server supports and the default voting behavior.
8080
Whether they are enabled depends on the Rules defined in the validated
@@ -360,6 +360,7 @@ extern uint256 const fixEmptyDID;
360360
extern uint256 const fixXChainRewardRounding;
361361
extern uint256 const fixPreviousTxnID;
362362
extern uint256 const fixAMMv1_1;
363+
extern uint256 const fixReducedOffersV2;
363364

364365
} // namespace ripple
365366

0 commit comments

Comments
 (0)