diff --git a/AMMOffer_8cpp_source.html b/AMMOffer_8cpp_source.html index 82165acec3..2b31a6eed2 100644 --- a/AMMOffer_8cpp_source.html +++ b/AMMOffer_8cpp_source.html @@ -186,101 +186,96 @@ $(document).ready(function() { init_codefold(0); });
92 // poolPays * poolGets < (poolPays - assetOut) * (poolGets + assetIn)
93 if (ammLiquidity_.multiPath())
94 {
-
95 if (auto const& rules = getCurrentTransactionRules();
-
96 rules && rules->enabled(fixReducedOffersV1))
-
97 // It turns out that the ceil_out implementation has some slop in
-
98 // it. ceil_out_strict removes that slop. But removing that slop
-
99 // affects transaction outcomes, so the change must be made using
-
100 // an amendment.
-
101 return quality().ceil_out_strict(offrAmt, limit, roundUp);
-
102 return quality().ceil_out(offrAmt, limit);
-
103 }
-
104 // Change the offer size according to the conservation function. The offer
-
105 // quality is increased in this case, but it doesn't matter since there is
-
106 // only one path.
-
107 return {swapAssetOut(balances_, limit, ammLiquidity_.tradingFee()), limit};
-
108}
+
95 // It turns out that the ceil_out implementation has some slop in
+
96 // it, which ceil_out_strict removes.
+
97 return quality().ceil_out_strict(offrAmt, limit, roundUp);
+
98 }
+
99 // Change the offer size according to the conservation function. The offer
+
100 // quality is increased in this case, but it doesn't matter since there is
+
101 // only one path.
+
102 return {swapAssetOut(balances_, limit, ammLiquidity_.tradingFee()), limit};
+
103}
+ +
104
+
105template <typename TIn, typename TOut>
+
106TAmounts<TIn, TOut>
+
+
107AMMOffer<TIn, TOut>::limitIn(
+
108 TAmounts<TIn, TOut> const& offrAmt,
+
109 TIn const& limit,
+
110 bool roundUp) const
+
111{
+
112 // See the comments above in limitOut().
+
113 if (ammLiquidity_.multiPath())
+
114 {
+
115 if (auto const& rules = getCurrentTransactionRules();
+
116 rules && rules->enabled(fixReducedOffersV2))
+
117 return quality().ceil_in_strict(offrAmt, limit, roundUp);
+
118
+
119 return quality().ceil_in(offrAmt, limit);
+
120 }
+
121 return {limit, swapAssetIn(balances_, limit, ammLiquidity_.tradingFee())};
+
122}
-
109
-
110template <typename TIn, typename TOut>
-
111TAmounts<TIn, TOut>
-
-
112AMMOffer<TIn, TOut>::limitIn(
-
113 TAmounts<TIn, TOut> const& offrAmt,
-
114 TIn const& limit,
-
115 bool roundUp) const
-
116{
-
117 // See the comments above in limitOut().
-
118 if (ammLiquidity_.multiPath())
-
119 {
-
120 if (auto const& rules = getCurrentTransactionRules();
-
121 rules && rules->enabled(fixReducedOffersV2))
-
122 return quality().ceil_in_strict(offrAmt, limit, roundUp);
123
-
124 return quality().ceil_in(offrAmt, limit);
-
125 }
-
126 return {limit, swapAssetIn(balances_, limit, ammLiquidity_.tradingFee())};
-
127}
+
124template <typename TIn, typename TOut>
+
125QualityFunction
+
+ +
127{
+
128 if (ammLiquidity_.multiPath())
+ +
130 return QualityFunction{
+
131 balances_, ammLiquidity_.tradingFee(), QualityFunction::AMMTag{}};
+
132}
-
128
-
129template <typename TIn, typename TOut>
-
130QualityFunction
-
- -
132{
-
133 if (ammLiquidity_.multiPath())
- -
135 return QualityFunction{
-
136 balances_, ammLiquidity_.tradingFee(), QualityFunction::AMMTag{}};
-
137}
-
-
138
-
139template <typename TIn, typename TOut>
-
140bool
-
- -
142 TAmounts<TIn, TOut> const& consumed,
-
143 beast::Journal j) const
-
144{
-
145 if (consumed.in > amounts_.in || consumed.out > amounts_.out)
-
146 {
-
147 JLOG(j.error()) << "AMMOffer::checkInvariant failed: consumed "
-
148 << to_string(consumed.in) << " "
-
149 << to_string(consumed.out) << " amounts "
-
150 << to_string(amounts_.in) << " "
-
151 << to_string(amounts_.out);
-
152
-
153 return false;
-
154 }
+
133
+
134template <typename TIn, typename TOut>
+
135bool
+
+ +
137 TAmounts<TIn, TOut> const& consumed,
+
138 beast::Journal j) const
+
139{
+
140 if (consumed.in > amounts_.in || consumed.out > amounts_.out)
+
141 {
+
142 JLOG(j.error()) << "AMMOffer::checkInvariant failed: consumed "
+
143 << to_string(consumed.in) << " "
+
144 << to_string(consumed.out) << " amounts "
+
145 << to_string(amounts_.in) << " "
+
146 << to_string(amounts_.out);
+
147
+
148 return false;
+
149 }
+
150
+
151 Number const product = balances_.in * balances_.out;
+
152 auto const newBalances = TAmounts<TIn, TOut>{
+
153 balances_.in + consumed.in, balances_.out - consumed.out};
+
154 Number const newProduct = newBalances.in * newBalances.out;
155
-
156 Number const product = balances_.in * balances_.out;
-
157 auto const newBalances = TAmounts<TIn, TOut>{
-
158 balances_.in + consumed.in, balances_.out - consumed.out};
-
159 Number const newProduct = newBalances.in * newBalances.out;
-
160
-
161 if (newProduct >= product ||
-
162 withinRelativeDistance(product, newProduct, Number{1, -7}))
-
163 return true;
-
164
-
165 JLOG(j.error()) << "AMMOffer::checkInvariant failed: balances "
-
166 << to_string(balances_.in) << " "
-
167 << to_string(balances_.out) << " new balances "
-
168 << to_string(newBalances.in) << " "
-
169 << to_string(newBalances.out) << " product/newProduct "
-
170 << product << " " << newProduct << " diff "
-
171 << (product != Number{0}
-
172 ? to_string((product - newProduct) / product)
-
173 : "undefined");
-
174 return false;
-
175}
+
156 if (newProduct >= product ||
+
157 withinRelativeDistance(product, newProduct, Number{1, -7}))
+
158 return true;
+
159
+
160 JLOG(j.error()) << "AMMOffer::checkInvariant failed: balances "
+
161 << to_string(balances_.in) << " "
+
162 << to_string(balances_.out) << " new balances "
+
163 << to_string(newBalances.in) << " "
+
164 << to_string(newBalances.out) << " product/newProduct "
+
165 << product << " " << newProduct << " diff "
+
166 << (product != Number{0}
+
167 ? to_string((product - newProduct) / product)
+
168 : "undefined");
+
169 return false;
+
170}
+
171
+
172template class AMMOffer<STAmount, STAmount>;
+
173template class AMMOffer<IOUAmount, IOUAmount>;
+
174template class AMMOffer<XRPAmount, IOUAmount>;
+
175template class AMMOffer<IOUAmount, XRPAmount>;
176
-
177template class AMMOffer<STAmount, STAmount>;
-
178template class AMMOffer<IOUAmount, IOUAmount>;
-
179template class AMMOffer<XRPAmount, IOUAmount>;
-
180template class AMMOffer<IOUAmount, XRPAmount>;
-
181
-
182} // namespace ripple
+
177} // namespace ripple
A generic endpoint for log messages.
Definition Journal.h:60
Stream error() const
Definition Journal.h:346
AMMLiquidity class provides AMM offers to BookStep class.
@@ -288,12 +283,12 @@ $(document).ready(function() { init_codefold(0); });
AMMOffer(AMMLiquidity< TIn, TOut > const &ammLiquidity, TAmounts< TIn, TOut > const &amounts, TAmounts< TIn, TOut > const &balances, Quality const &quality)
Definition AMMOffer.cpp:28
AccountID const & owner() const
Definition AMMOffer.cpp:50
TAmounts< TIn, TOut > limitOut(TAmounts< TIn, TOut > const &offrAmt, TOut const &limit, bool roundUp) const
Limit out of the provided offer.
Definition AMMOffer.cpp:82
-
bool checkInvariant(TAmounts< TIn, TOut > const &consumed, beast::Journal j) const
Check the new pool product is greater or equal to the old pool product or if decreases then within so...
Definition AMMOffer.cpp:141
+
bool checkInvariant(TAmounts< TIn, TOut > const &consumed, beast::Journal j) const
Check the new pool product is greater or equal to the old pool product or if decreases then within so...
Definition AMMOffer.cpp:136
void consume(ApplyView &view, TAmounts< TIn, TOut > const &consumed)
Definition AMMOffer.cpp:64
-
TAmounts< TIn, TOut > limitIn(TAmounts< TIn, TOut > const &offrAmt, TIn const &limit, bool roundUp) const
Limit in of the provided offer.
Definition AMMOffer.cpp:112
+
TAmounts< TIn, TOut > limitIn(TAmounts< TIn, TOut > const &offrAmt, TIn const &limit, bool roundUp) const
Limit in of the provided offer.
Definition AMMOffer.cpp:107
Issue const & issueIn() const
Definition AMMOffer.cpp:43
TAmounts< TIn, TOut > const & amount() const
Definition AMMOffer.cpp:57
-
QualityFunction getQualityFunc() const
Definition AMMOffer.cpp:131
+
QualityFunction getQualityFunc() const
Definition AMMOffer.cpp:126
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:143
A currency issued by an account.
Definition Issue.h:33
diff --git a/AMMOffer_8h_source.html b/AMMOffer_8h_source.html index b5310f28fb..1f6af0be8e 100644 --- a/AMMOffer_8h_source.html +++ b/AMMOffer_8h_source.html @@ -240,17 +240,17 @@ $(document).ready(function() { init_codefold(0); });
static std::pair< std::uint32_t, std::uint32_t > adjustRates(std::uint32_t ofrInRate, std::uint32_t ofrOutRate)
Definition AMMOffer.h:136
AMMLiquidity< TIn, TOut > const & ammLiquidity_
Definition AMMOffer.h:42
Quality quality() const noexcept
Definition AMMOffer.h:69
-
bool checkInvariant(TAmounts< TIn, TOut > const &consumed, beast::Journal j) const
Check the new pool product is greater or equal to the old pool product or if decreases then within so...
Definition AMMOffer.cpp:141
+
bool checkInvariant(TAmounts< TIn, TOut > const &consumed, beast::Journal j) const
Check the new pool product is greater or equal to the old pool product or if decreases then within so...
Definition AMMOffer.cpp:136
void consume(ApplyView &view, TAmounts< TIn, TOut > const &consumed)
Definition AMMOffer.cpp:64
-
TAmounts< TIn, TOut > limitIn(TAmounts< TIn, TOut > const &offrAmt, TIn const &limit, bool roundUp) const
Limit in of the provided offer.
Definition AMMOffer.cpp:112
+
TAmounts< TIn, TOut > limitIn(TAmounts< TIn, TOut > const &offrAmt, TIn const &limit, bool roundUp) const
Limit in of the provided offer.
Definition AMMOffer.cpp:107
std::optional< uint256 > key() const
Definition AMMOffer.h:81
TAmounts< TIn, TOut > const balances_
Definition AMMOffer.h:54
TAmounts< TIn, TOut > const amounts_
Definition AMMOffer.h:52
bool fully_consumed() const
Definition AMMOffer.h:93
Issue const & issueIn() const
Definition AMMOffer.cpp:43
TAmounts< TIn, TOut > const & amount() const
Definition AMMOffer.cpp:57
-
QualityFunction getQualityFunc() const
Definition AMMOffer.cpp:131
+
QualityFunction getQualityFunc() const
Definition AMMOffer.cpp:126
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:143
A currency issued by an account.
Definition Issue.h:33
Average quality of a path as a function of out: q(out) = m * out + b, where m = -1 / poolGets,...
diff --git a/CreateOffer_8cpp_source.html b/CreateOffer_8cpp_source.html index f7b7e8cc5a..7902f1ef92 100644 --- a/CreateOffer_8cpp_source.html +++ b/CreateOffer_8cpp_source.html @@ -573,495 +573,481 @@ $(document).ready(function() { init_codefold(0); });
477 // what is a good threshold to check?
478 afterCross.in.clear();
479
-
480 afterCross.out = [&]() {
-
481 // Careful analysis showed that rounding up this
-
482 // divRound result could lead to placing a reduced
-
483 // offer in the ledger that blocks order books. So
-
484 // the fixReducedOffersV1 amendment changes the
-
485 // behavior to round down instead.
-
486 if (psb.rules().enabled(fixReducedOffersV1))
-
487 return divRoundStrict(
-
488 afterCross.in,
-
489 rate,
-
490 takerAmount.out.issue(),
-
491 false);
-
492
-
493 return divRound(
-
494 afterCross.in, rate, takerAmount.out.issue(), true);
-
495 }();
+
480 afterCross.out = divRoundStrict(
+
481 afterCross.in, rate, takerAmount.out.issue(), false);
+
482 }
+
483 else
+
484 {
+
485 // If not selling, we scale the input based on the
+
486 // remaining output. This too preserves the offer
+
487 // Quality.
+
488 afterCross.out -= result.actualAmountOut;
+
489 XRPL_ASSERT(
+
490 afterCross.out >= beast::zero,
+
491 "ripple::CreateOffer::flowCross : minimum offer");
+
492 if (afterCross.out < beast::zero)
+
493 afterCross.out.clear();
+
494 afterCross.in = mulRound(
+
495 afterCross.out, rate, takerAmount.in.issue(), true);
496 }
-
497 else
-
498 {
-
499 // If not selling, we scale the input based on the
-
500 // remaining output. This too preserves the offer
-
501 // Quality.
-
502 afterCross.out -= result.actualAmountOut;
-
503 XRPL_ASSERT(
-
504 afterCross.out >= beast::zero,
-
505 "ripple::CreateOffer::flowCross : minimum offer");
-
506 if (afterCross.out < beast::zero)
-
507 afterCross.out.clear();
-
508 afterCross.in = mulRound(
-
509 afterCross.out, rate, takerAmount.in.issue(), true);
-
510 }
-
511 }
-
512 }
-
513
-
514 // Return how much of the offer is left.
-
515 return {tesSUCCESS, afterCross};
-
516 }
-
517 catch (std::exception const& e)
-
518 {
-
519 JLOG(j_.error()) << "Exception during offer crossing: " << e.what();
-
520 }
-
521 return {tecINTERNAL, takerAmount};
-
522}
+
497 }
+
498 }
+
499
+
500 // Return how much of the offer is left.
+
501 return {tesSUCCESS, afterCross};
+
502 }
+
503 catch (std::exception const& e)
+
504 {
+
505 JLOG(j_.error()) << "Exception during offer crossing: " << e.what();
+
506 }
+
507 return {tecINTERNAL, takerAmount};
+
508}
-
523
-
524std::string
-
- -
526{
-
527 std::string txt = amount.getText();
-
528 txt += "/";
-
529 txt += to_string(amount.issue().currency);
-
530 return txt;
-
531}
+
509
+ +
+ +
512{
+
513 std::string txt = amount.getText();
+
514 txt += "/";
+
515 txt += to_string(amount.issue().currency);
+
516 return txt;
+
517}
-
532
-
533TER
-
- -
535 Sandbox& sb,
- -
537 Keylet const& offerKey,
-
538 STAmount const& saTakerPays,
-
539 STAmount const& saTakerGets,
-
540 std::function<void(SLE::ref, std::optional<uint256>)> const& setDir)
-
541{
-
542 if (!sleOffer->isFieldPresent(sfDomainID))
-
543 return tecINTERNAL; // LCOV_EXCL_LINE
-
544
-
545 // set hybrid flag
-
546 sleOffer->setFlag(lsfHybrid);
-
547
-
548 // if offer is hybrid, need to also place into open offer dir
-
549 Book const book{saTakerPays.issue(), saTakerGets.issue(), std::nullopt};
-
550
-
551 auto dir =
-
552 keylet::quality(keylet::book(book), getRate(saTakerGets, saTakerPays));
-
553 bool const bookExists = sb.exists(dir);
-
554
-
555 auto const bookNode = sb.dirAppend(dir, offerKey, [&](SLE::ref sle) {
-
556 // don't set domainID on the directory object since this directory is
-
557 // for open book
-
558 setDir(sle, std::nullopt);
-
559 });
-
560
-
561 if (!bookNode)
-
562 {
-
563 JLOG(j_.debug())
-
564 << "final result: failed to add hybrid offer to open book";
-
565 return tecDIR_FULL; // LCOV_EXCL_LINE
-
566 }
-
567
-
568 STArray bookArr(sfAdditionalBooks, 1);
-
569 auto bookInfo = STObject::makeInnerObject(sfBook);
-
570 bookInfo.setFieldH256(sfBookDirectory, dir.key);
-
571 bookInfo.setFieldU64(sfBookNode, *bookNode);
-
572 bookArr.push_back(std::move(bookInfo));
+
518
+
519TER
+
+ +
521 Sandbox& sb,
+ +
523 Keylet const& offerKey,
+
524 STAmount const& saTakerPays,
+
525 STAmount const& saTakerGets,
+
526 std::function<void(SLE::ref, std::optional<uint256>)> const& setDir)
+
527{
+
528 if (!sleOffer->isFieldPresent(sfDomainID))
+
529 return tecINTERNAL; // LCOV_EXCL_LINE
+
530
+
531 // set hybrid flag
+
532 sleOffer->setFlag(lsfHybrid);
+
533
+
534 // if offer is hybrid, need to also place into open offer dir
+
535 Book const book{saTakerPays.issue(), saTakerGets.issue(), std::nullopt};
+
536
+
537 auto dir =
+
538 keylet::quality(keylet::book(book), getRate(saTakerGets, saTakerPays));
+
539 bool const bookExists = sb.exists(dir);
+
540
+
541 auto const bookNode = sb.dirAppend(dir, offerKey, [&](SLE::ref sle) {
+
542 // don't set domainID on the directory object since this directory is
+
543 // for open book
+
544 setDir(sle, std::nullopt);
+
545 });
+
546
+
547 if (!bookNode)
+
548 {
+
549 JLOG(j_.debug())
+
550 << "final result: failed to add hybrid offer to open book";
+
551 return tecDIR_FULL; // LCOV_EXCL_LINE
+
552 }
+
553
+
554 STArray bookArr(sfAdditionalBooks, 1);
+
555 auto bookInfo = STObject::makeInnerObject(sfBook);
+
556 bookInfo.setFieldH256(sfBookDirectory, dir.key);
+
557 bookInfo.setFieldU64(sfBookNode, *bookNode);
+
558 bookArr.push_back(std::move(bookInfo));
+
559
+
560 if (!bookExists)
+ +
562
+
563 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
+
564 return tesSUCCESS;
+
565}
+
+
566
+ +
+ +
569{
+
570 using beast::zero;
+
571
+
572 std::uint32_t const uTxFlags = ctx_.tx.getFlags();
573
-
574 if (!bookExists)
- -
576
-
577 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
-
578 return tesSUCCESS;
-
579}
-
-
580
- -
- -
583{
-
584 using beast::zero;
+
574 bool const bPassive(uTxFlags & tfPassive);
+
575 bool const bImmediateOrCancel(uTxFlags & tfImmediateOrCancel);
+
576 bool const bFillOrKill(uTxFlags & tfFillOrKill);
+
577 bool const bSell(uTxFlags & tfSell);
+
578 bool const bHybrid(uTxFlags & tfHybrid);
+
579
+
580 auto saTakerPays = ctx_.tx[sfTakerPays];
+
581 auto saTakerGets = ctx_.tx[sfTakerGets];
+
582 auto const domainID = ctx_.tx[~sfDomainID];
+
583
+
584 auto const cancelSequence = ctx_.tx[~sfOfferSequence];
585
-
586 std::uint32_t const uTxFlags = ctx_.tx.getFlags();
-
587
-
588 bool const bPassive(uTxFlags & tfPassive);
-
589 bool const bImmediateOrCancel(uTxFlags & tfImmediateOrCancel);
-
590 bool const bFillOrKill(uTxFlags & tfFillOrKill);
-
591 bool const bSell(uTxFlags & tfSell);
-
592 bool const bHybrid(uTxFlags & tfHybrid);
-
593
-
594 auto saTakerPays = ctx_.tx[sfTakerPays];
-
595 auto saTakerGets = ctx_.tx[sfTakerGets];
-
596 auto const domainID = ctx_.tx[~sfDomainID];
-
597
-
598 auto const cancelSequence = ctx_.tx[~sfOfferSequence];
-
599
-
600 // Note that we we use the value from the sequence or ticket as the
-
601 // offer sequence. For more explanation see comments in SeqProxy.h.
-
602 auto const offerSequence = ctx_.tx.getSeqValue();
-
603
-
604 // This is the original rate of the offer, and is the rate at which
-
605 // it will be placed, even if crossing offers change the amounts that
-
606 // end up on the books.
-
607 auto uRate = getRate(saTakerGets, saTakerPays);
-
608
-
609 auto viewJ = ctx_.app.journal("View");
-
610
-
611 TER result = tesSUCCESS;
-
612
-
613 // Process a cancellation request that's passed along with an offer.
-
614 if (cancelSequence)
-
615 {
-
616 auto const sleCancel =
-
617 sb.peek(keylet::offer(account_, *cancelSequence));
-
618
-
619 // It's not an error to not find the offer to cancel: it might have
-
620 // been consumed or removed. If it is found, however, it's an error
-
621 // to fail to delete it.
-
622 if (sleCancel)
-
623 {
-
624 JLOG(j_.debug()) << "Create cancels order " << *cancelSequence;
-
625 result = offerDelete(sb, sleCancel, viewJ);
-
626 }
-
627 }
-
628
-
629 auto const expiration = ctx_.tx[~sfExpiration];
-
630
-
631 if (hasExpired(sb, expiration))
-
632 {
-
633 // If the offer has expired, the transaction has successfully
-
634 // done nothing, so short circuit from here.
-
635 //
-
636 // The return code change is attached to featureDepositPreauth as a
-
637 // convenience. The change is not big enough to deserve a fix code.
-
638 TER const ter{
-
639 sb.rules().enabled(featureDepositPreauth) ? TER{tecEXPIRED}
-
640 : TER{tesSUCCESS}};
-
641 return {ter, true};
-
642 }
-
643
-
644 bool const bOpenLedger = sb.open();
-
645 bool crossed = false;
-
646
-
647 if (result == tesSUCCESS)
-
648 {
-
649 // If a tick size applies, round the offer to the tick size
-
650 auto const& uPaysIssuerID = saTakerPays.getIssuer();
-
651 auto const& uGetsIssuerID = saTakerGets.getIssuer();
-
652
-
653 std::uint8_t uTickSize = Quality::maxTickSize;
-
654 if (!isXRP(uPaysIssuerID))
-
655 {
-
656 auto const sle = sb.read(keylet::account(uPaysIssuerID));
-
657 if (sle && sle->isFieldPresent(sfTickSize))
-
658 uTickSize = std::min(uTickSize, (*sle)[sfTickSize]);
-
659 }
-
660 if (!isXRP(uGetsIssuerID))
-
661 {
-
662 auto const sle = sb.read(keylet::account(uGetsIssuerID));
-
663 if (sle && sle->isFieldPresent(sfTickSize))
-
664 uTickSize = std::min(uTickSize, (*sle)[sfTickSize]);
-
665 }
-
666 if (uTickSize < Quality::maxTickSize)
-
667 {
-
668 auto const rate =
-
669 Quality{saTakerGets, saTakerPays}.round(uTickSize).rate();
-
670
-
671 // We round the side that's not exact,
-
672 // just as if the offer happened to execute
-
673 // at a slightly better (for the placer) rate
-
674 if (bSell)
-
675 {
-
676 // this is a sell, round taker pays
-
677 saTakerPays = multiply(saTakerGets, rate, saTakerPays.issue());
-
678 }
-
679 else
-
680 {
-
681 // this is a buy, round taker gets
-
682 saTakerGets = divide(saTakerPays, rate, saTakerGets.issue());
-
683 }
-
684 if (!saTakerGets || !saTakerPays)
-
685 {
-
686 JLOG(j_.debug()) << "Offer rounded to zero";
-
687 return {result, true};
-
688 }
-
689
-
690 uRate = getRate(saTakerGets, saTakerPays);
-
691 }
-
692
-
693 // We reverse pays and gets because during crossing we are taking.
-
694 Amounts const takerAmount(saTakerGets, saTakerPays);
-
695
-
696 JLOG(j_.debug()) << "Attempting cross: "
-
697 << to_string(takerAmount.in.issue()) << " -> "
-
698 << to_string(takerAmount.out.issue());
-
699
-
700 if (auto stream = j_.trace())
-
701 {
-
702 stream << " mode: " << (bPassive ? "passive " : "")
-
703 << (bSell ? "sell" : "buy");
-
704 stream << " in: " << format_amount(takerAmount.in);
-
705 stream << " out: " << format_amount(takerAmount.out);
-
706 }
-
707
-
708 // The amount of the offer that is unfilled after crossing has been
-
709 // performed. It may be equal to the original amount (didn't cross),
-
710 // empty (fully crossed), or something in-between.
-
711 Amounts place_offer;
-
712 PaymentSandbox psbFlow{&sb};
-
713 PaymentSandbox psbCancelFlow{&sbCancel};
-
714
-
715 std::tie(result, place_offer) =
-
716 flowCross(psbFlow, psbCancelFlow, takerAmount, domainID);
-
717 psbFlow.apply(sb);
-
718 psbCancelFlow.apply(sbCancel);
+
586 // Note that we we use the value from the sequence or ticket as the
+
587 // offer sequence. For more explanation see comments in SeqProxy.h.
+
588 auto const offerSequence = ctx_.tx.getSeqValue();
+
589
+
590 // This is the original rate of the offer, and is the rate at which
+
591 // it will be placed, even if crossing offers change the amounts that
+
592 // end up on the books.
+
593 auto uRate = getRate(saTakerGets, saTakerPays);
+
594
+
595 auto viewJ = ctx_.app.journal("View");
+
596
+
597 TER result = tesSUCCESS;
+
598
+
599 // Process a cancellation request that's passed along with an offer.
+
600 if (cancelSequence)
+
601 {
+
602 auto const sleCancel =
+
603 sb.peek(keylet::offer(account_, *cancelSequence));
+
604
+
605 // It's not an error to not find the offer to cancel: it might have
+
606 // been consumed or removed. If it is found, however, it's an error
+
607 // to fail to delete it.
+
608 if (sleCancel)
+
609 {
+
610 JLOG(j_.debug()) << "Create cancels order " << *cancelSequence;
+
611 result = offerDelete(sb, sleCancel, viewJ);
+
612 }
+
613 }
+
614
+
615 auto const expiration = ctx_.tx[~sfExpiration];
+
616
+
617 if (hasExpired(sb, expiration))
+
618 {
+
619 // If the offer has expired, the transaction has successfully
+
620 // done nothing, so short circuit from here.
+
621 //
+
622 // The return code change is attached to featureDepositPreauth as a
+
623 // convenience. The change is not big enough to deserve a fix code.
+
624 TER const ter{
+
625 sb.rules().enabled(featureDepositPreauth) ? TER{tecEXPIRED}
+
626 : TER{tesSUCCESS}};
+
627 return {ter, true};
+
628 }
+
629
+
630 bool const bOpenLedger = sb.open();
+
631 bool crossed = false;
+
632
+
633 if (result == tesSUCCESS)
+
634 {
+
635 // If a tick size applies, round the offer to the tick size
+
636 auto const& uPaysIssuerID = saTakerPays.getIssuer();
+
637 auto const& uGetsIssuerID = saTakerGets.getIssuer();
+
638
+
639 std::uint8_t uTickSize = Quality::maxTickSize;
+
640 if (!isXRP(uPaysIssuerID))
+
641 {
+
642 auto const sle = sb.read(keylet::account(uPaysIssuerID));
+
643 if (sle && sle->isFieldPresent(sfTickSize))
+
644 uTickSize = std::min(uTickSize, (*sle)[sfTickSize]);
+
645 }
+
646 if (!isXRP(uGetsIssuerID))
+
647 {
+
648 auto const sle = sb.read(keylet::account(uGetsIssuerID));
+
649 if (sle && sle->isFieldPresent(sfTickSize))
+
650 uTickSize = std::min(uTickSize, (*sle)[sfTickSize]);
+
651 }
+
652 if (uTickSize < Quality::maxTickSize)
+
653 {
+
654 auto const rate =
+
655 Quality{saTakerGets, saTakerPays}.round(uTickSize).rate();
+
656
+
657 // We round the side that's not exact,
+
658 // just as if the offer happened to execute
+
659 // at a slightly better (for the placer) rate
+
660 if (bSell)
+
661 {
+
662 // this is a sell, round taker pays
+
663 saTakerPays = multiply(saTakerGets, rate, saTakerPays.issue());
+
664 }
+
665 else
+
666 {
+
667 // this is a buy, round taker gets
+
668 saTakerGets = divide(saTakerPays, rate, saTakerGets.issue());
+
669 }
+
670 if (!saTakerGets || !saTakerPays)
+
671 {
+
672 JLOG(j_.debug()) << "Offer rounded to zero";
+
673 return {result, true};
+
674 }
+
675
+
676 uRate = getRate(saTakerGets, saTakerPays);
+
677 }
+
678
+
679 // We reverse pays and gets because during crossing we are taking.
+
680 Amounts const takerAmount(saTakerGets, saTakerPays);
+
681
+
682 JLOG(j_.debug()) << "Attempting cross: "
+
683 << to_string(takerAmount.in.issue()) << " -> "
+
684 << to_string(takerAmount.out.issue());
+
685
+
686 if (auto stream = j_.trace())
+
687 {
+
688 stream << " mode: " << (bPassive ? "passive " : "")
+
689 << (bSell ? "sell" : "buy");
+
690 stream << " in: " << format_amount(takerAmount.in);
+
691 stream << " out: " << format_amount(takerAmount.out);
+
692 }
+
693
+
694 // The amount of the offer that is unfilled after crossing has been
+
695 // performed. It may be equal to the original amount (didn't cross),
+
696 // empty (fully crossed), or something in-between.
+
697 Amounts place_offer;
+
698 PaymentSandbox psbFlow{&sb};
+
699 PaymentSandbox psbCancelFlow{&sbCancel};
+
700
+
701 std::tie(result, place_offer) =
+
702 flowCross(psbFlow, psbCancelFlow, takerAmount, domainID);
+
703 psbFlow.apply(sb);
+
704 psbCancelFlow.apply(sbCancel);
+
705
+
706 // We expect the implementation of cross to succeed
+
707 // or give a tec.
+
708 XRPL_ASSERT(
+
709 result == tesSUCCESS || isTecClaim(result),
+
710 "ripple::CreateOffer::applyGuts : result is tesSUCCESS or "
+
711 "tecCLAIM");
+
712
+
713 if (auto stream = j_.trace())
+
714 {
+
715 stream << "Cross result: " << transToken(result);
+
716 stream << " in: " << format_amount(place_offer.in);
+
717 stream << " out: " << format_amount(place_offer.out);
+
718 }
719
-
720 // We expect the implementation of cross to succeed
-
721 // or give a tec.
-
722 XRPL_ASSERT(
-
723 result == tesSUCCESS || isTecClaim(result),
-
724 "ripple::CreateOffer::applyGuts : result is tesSUCCESS or "
-
725 "tecCLAIM");
-
726
-
727 if (auto stream = j_.trace())
-
728 {
-
729 stream << "Cross result: " << transToken(result);
-
730 stream << " in: " << format_amount(place_offer.in);
-
731 stream << " out: " << format_amount(place_offer.out);
-
732 }
-
733
-
734 if (result == tecFAILED_PROCESSING && bOpenLedger)
-
735 result = telFAILED_PROCESSING;
-
736
-
737 if (result != tesSUCCESS)
-
738 {
-
739 JLOG(j_.debug()) << "final result: " << transToken(result);
-
740 return {result, true};
-
741 }
-
742
-
743 XRPL_ASSERT(
-
744 saTakerGets.issue() == place_offer.in.issue(),
-
745 "ripple::CreateOffer::applyGuts : taker gets issue match");
-
746 XRPL_ASSERT(
-
747 saTakerPays.issue() == place_offer.out.issue(),
-
748 "ripple::CreateOffer::applyGuts : taker pays issue match");
-
749
-
750 if (takerAmount != place_offer)
-
751 crossed = true;
-
752
-
753 // The offer that we need to place after offer crossing should
-
754 // never be negative. If it is, something went very very wrong.
-
755 if (place_offer.in < zero || place_offer.out < zero)
-
756 {
-
757 JLOG(j_.fatal()) << "Cross left offer negative!"
-
758 << " in: " << format_amount(place_offer.in)
-
759 << " out: " << format_amount(place_offer.out);
-
760 return {tefINTERNAL, true};
-
761 }
-
762
-
763 if (place_offer.in == zero || place_offer.out == zero)
-
764 {
-
765 JLOG(j_.debug()) << "Offer fully crossed!";
-
766 return {result, true};
-
767 }
-
768
-
769 // We now need to adjust the offer to reflect the amount left after
-
770 // crossing. We reverse in and out here, since during crossing we
-
771 // were the taker.
-
772 saTakerPays = place_offer.out;
-
773 saTakerGets = place_offer.in;
-
774 }
-
775
-
776 XRPL_ASSERT(
-
777 saTakerPays > zero && saTakerGets > zero,
-
778 "ripple::CreateOffer::applyGuts : taker pays and gets positive");
-
779
-
780 if (result != tesSUCCESS)
-
781 {
-
782 JLOG(j_.debug()) << "final result: " << transToken(result);
-
783 return {result, true};
-
784 }
-
785
-
786 if (auto stream = j_.trace())
-
787 {
-
788 stream << "Place" << (crossed ? " remaining " : " ") << "offer:";
-
789 stream << " Pays: " << saTakerPays.getFullText();
-
790 stream << " Gets: " << saTakerGets.getFullText();
-
791 }
-
792
-
793 // For 'fill or kill' offers, failure to fully cross means that the
-
794 // entire operation should be aborted, with only fees paid.
-
795 if (bFillOrKill)
-
796 {
-
797 JLOG(j_.trace()) << "Fill or Kill: offer killed";
-
798 return {tecKILLED, false};
+
720 if (result == tecFAILED_PROCESSING && bOpenLedger)
+
721 result = telFAILED_PROCESSING;
+
722
+
723 if (result != tesSUCCESS)
+
724 {
+
725 JLOG(j_.debug()) << "final result: " << transToken(result);
+
726 return {result, true};
+
727 }
+
728
+
729 XRPL_ASSERT(
+
730 saTakerGets.issue() == place_offer.in.issue(),
+
731 "ripple::CreateOffer::applyGuts : taker gets issue match");
+
732 XRPL_ASSERT(
+
733 saTakerPays.issue() == place_offer.out.issue(),
+
734 "ripple::CreateOffer::applyGuts : taker pays issue match");
+
735
+
736 if (takerAmount != place_offer)
+
737 crossed = true;
+
738
+
739 // The offer that we need to place after offer crossing should
+
740 // never be negative. If it is, something went very very wrong.
+
741 if (place_offer.in < zero || place_offer.out < zero)
+
742 {
+
743 JLOG(j_.fatal()) << "Cross left offer negative!"
+
744 << " in: " << format_amount(place_offer.in)
+
745 << " out: " << format_amount(place_offer.out);
+
746 return {tefINTERNAL, true};
+
747 }
+
748
+
749 if (place_offer.in == zero || place_offer.out == zero)
+
750 {
+
751 JLOG(j_.debug()) << "Offer fully crossed!";
+
752 return {result, true};
+
753 }
+
754
+
755 // We now need to adjust the offer to reflect the amount left after
+
756 // crossing. We reverse in and out here, since during crossing we
+
757 // were the taker.
+
758 saTakerPays = place_offer.out;
+
759 saTakerGets = place_offer.in;
+
760 }
+
761
+
762 XRPL_ASSERT(
+
763 saTakerPays > zero && saTakerGets > zero,
+
764 "ripple::CreateOffer::applyGuts : taker pays and gets positive");
+
765
+
766 if (result != tesSUCCESS)
+
767 {
+
768 JLOG(j_.debug()) << "final result: " << transToken(result);
+
769 return {result, true};
+
770 }
+
771
+
772 if (auto stream = j_.trace())
+
773 {
+
774 stream << "Place" << (crossed ? " remaining " : " ") << "offer:";
+
775 stream << " Pays: " << saTakerPays.getFullText();
+
776 stream << " Gets: " << saTakerGets.getFullText();
+
777 }
+
778
+
779 // For 'fill or kill' offers, failure to fully cross means that the
+
780 // entire operation should be aborted, with only fees paid.
+
781 if (bFillOrKill)
+
782 {
+
783 JLOG(j_.trace()) << "Fill or Kill: offer killed";
+
784 return {tecKILLED, false};
+
785 }
+
786
+
787 // For 'immediate or cancel' offers, the amount remaining doesn't get
+
788 // placed - it gets canceled and the operation succeeds.
+
789 if (bImmediateOrCancel)
+
790 {
+
791 JLOG(j_.trace()) << "Immediate or cancel: offer canceled";
+
792 if (!crossed && sb.rules().enabled(featureImmediateOfferKilled))
+
793 // If the ImmediateOfferKilled amendment is enabled, any
+
794 // ImmediateOrCancel offer that transfers absolutely no funds
+
795 // returns tecKILLED rather than tesSUCCESS. Motivation for the
+
796 // change is here: https://github.com/ripple/rippled/issues/4115
+
797 return {tecKILLED, false};
+
798 return {tesSUCCESS, true};
799 }
800
-
801 // For 'immediate or cancel' offers, the amount remaining doesn't get
-
802 // placed - it gets canceled and the operation succeeds.
-
803 if (bImmediateOrCancel)
-
804 {
-
805 JLOG(j_.trace()) << "Immediate or cancel: offer canceled";
-
806 if (!crossed && sb.rules().enabled(featureImmediateOfferKilled))
-
807 // If the ImmediateOfferKilled amendment is enabled, any
-
808 // ImmediateOrCancel offer that transfers absolutely no funds
-
809 // returns tecKILLED rather than tesSUCCESS. Motivation for the
-
810 // change is here: https://github.com/ripple/rippled/issues/4115
-
811 return {tecKILLED, false};
-
812 return {tesSUCCESS, true};
-
813 }
-
814
-
815 auto const sleCreator = sb.peek(keylet::account(account_));
-
816 if (!sleCreator)
-
817 return {tefINTERNAL, false};
-
818
-
819 {
-
820 XRPAmount reserve =
-
821 sb.fees().accountReserve(sleCreator->getFieldU32(sfOwnerCount) + 1);
-
822
-
823 if (mPriorBalance < reserve)
-
824 {
-
825 // If we are here, the signing account had an insufficient reserve
-
826 // *prior* to our processing. If something actually crossed, then
-
827 // we allow this; otherwise, we just claim a fee.
-
828 if (!crossed)
-
829 result = tecINSUF_RESERVE_OFFER;
-
830
-
831 if (result != tesSUCCESS)
-
832 {
-
833 JLOG(j_.debug()) << "final result: " << transToken(result);
-
834 }
-
835
-
836 return {result, true};
-
837 }
-
838 }
-
839
-
840 // We need to place the remainder of the offer into its order book.
-
841 auto const offer_index = keylet::offer(account_, offerSequence);
-
842
-
843 // Add offer to owner's directory.
-
844 auto const ownerNode = sb.dirInsert(
- -
846
-
847 if (!ownerNode)
-
848 {
-
849 // LCOV_EXCL_START
-
850 JLOG(j_.debug())
-
851 << "final result: failed to add offer to owner's directory";
-
852 return {tecDIR_FULL, true};
-
853 // LCOV_EXCL_STOP
-
854 }
-
855
-
856 // Update owner count.
-
857 adjustOwnerCount(sb, sleCreator, 1, viewJ);
-
858
-
859 JLOG(j_.trace()) << "adding to book: " << to_string(saTakerPays.issue())
-
860 << " : " << to_string(saTakerGets.issue())
-
861 << (domainID ? (" : " + to_string(*domainID)) : "");
-
862
-
863 Book const book{saTakerPays.issue(), saTakerGets.issue(), domainID};
+
801 auto const sleCreator = sb.peek(keylet::account(account_));
+
802 if (!sleCreator)
+
803 return {tefINTERNAL, false};
+
804
+
805 {
+
806 XRPAmount reserve =
+
807 sb.fees().accountReserve(sleCreator->getFieldU32(sfOwnerCount) + 1);
+
808
+
809 if (mPriorBalance < reserve)
+
810 {
+
811 // If we are here, the signing account had an insufficient reserve
+
812 // *prior* to our processing. If something actually crossed, then
+
813 // we allow this; otherwise, we just claim a fee.
+
814 if (!crossed)
+
815 result = tecINSUF_RESERVE_OFFER;
+
816
+
817 if (result != tesSUCCESS)
+
818 {
+
819 JLOG(j_.debug()) << "final result: " << transToken(result);
+
820 }
+
821
+
822 return {result, true};
+
823 }
+
824 }
+
825
+
826 // We need to place the remainder of the offer into its order book.
+
827 auto const offer_index = keylet::offer(account_, offerSequence);
+
828
+
829 // Add offer to owner's directory.
+
830 auto const ownerNode = sb.dirInsert(
+ +
832
+
833 if (!ownerNode)
+
834 {
+
835 // LCOV_EXCL_START
+
836 JLOG(j_.debug())
+
837 << "final result: failed to add offer to owner's directory";
+
838 return {tecDIR_FULL, true};
+
839 // LCOV_EXCL_STOP
+
840 }
+
841
+
842 // Update owner count.
+
843 adjustOwnerCount(sb, sleCreator, 1, viewJ);
+
844
+
845 JLOG(j_.trace()) << "adding to book: " << to_string(saTakerPays.issue())
+
846 << " : " << to_string(saTakerGets.issue())
+
847 << (domainID ? (" : " + to_string(*domainID)) : "");
+
848
+
849 Book const book{saTakerPays.issue(), saTakerGets.issue(), domainID};
+
850
+
851 // Add offer to order book, using the original rate
+
852 // before any crossing occured.
+
853 //
+
854 // Regular offer - BookDirectory points to open directory
+
855 //
+
856 // Domain offer (w/o hyrbid) - BookDirectory points to domain
+
857 // directory
+
858 //
+
859 // Hybrid domain offer - BookDirectory points to domain directory,
+
860 // and AdditionalBooks field stores one entry that points to the open
+
861 // directory
+
862 auto dir = keylet::quality(keylet::book(book), uRate);
+
863 bool const bookExisted = static_cast<bool>(sb.peek(dir));
864
-
865 // Add offer to order book, using the original rate
-
866 // before any crossing occured.
-
867 //
-
868 // Regular offer - BookDirectory points to open directory
-
869 //
-
870 // Domain offer (w/o hyrbid) - BookDirectory points to domain
-
871 // directory
-
872 //
-
873 // Hybrid domain offer - BookDirectory points to domain directory,
-
874 // and AdditionalBooks field stores one entry that points to the open
-
875 // directory
-
876 auto dir = keylet::quality(keylet::book(book), uRate);
-
877 bool const bookExisted = static_cast<bool>(sb.peek(dir));
-
878
-
879 auto setBookDir = [&](SLE::ref sle,
-
880 std::optional<uint256> const& maybeDomain) {
-
881 sle->setFieldH160(sfTakerPaysCurrency, saTakerPays.issue().currency);
-
882 sle->setFieldH160(sfTakerPaysIssuer, saTakerPays.issue().account);
-
883 sle->setFieldH160(sfTakerGetsCurrency, saTakerGets.issue().currency);
-
884 sle->setFieldH160(sfTakerGetsIssuer, saTakerGets.issue().account);
-
885 sle->setFieldU64(sfExchangeRate, uRate);
-
886 if (maybeDomain)
-
887 sle->setFieldH256(sfDomainID, *maybeDomain);
-
888 };
-
889
-
890 auto const bookNode = sb.dirAppend(dir, offer_index, [&](SLE::ref sle) {
-
891 // sets domainID on book directory if it's a domain offer
-
892 setBookDir(sle, domainID);
-
893 });
-
894
-
895 if (!bookNode)
-
896 {
-
897 // LCOV_EXCL_START
-
898 JLOG(j_.debug()) << "final result: failed to add offer to book";
-
899 return {tecDIR_FULL, true};
-
900 // LCOV_EXCL_STOP
-
901 }
-
902
-
903 auto sleOffer = std::make_shared<SLE>(offer_index);
-
904 sleOffer->setAccountID(sfAccount, account_);
-
905 sleOffer->setFieldU32(sfSequence, offerSequence);
-
906 sleOffer->setFieldH256(sfBookDirectory, dir.key);
-
907 sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
-
908 sleOffer->setFieldAmount(sfTakerGets, saTakerGets);
-
909 sleOffer->setFieldU64(sfOwnerNode, *ownerNode);
-
910 sleOffer->setFieldU64(sfBookNode, *bookNode);
-
911 if (expiration)
-
912 sleOffer->setFieldU32(sfExpiration, *expiration);
-
913 if (bPassive)
-
914 sleOffer->setFlag(lsfPassive);
-
915 if (bSell)
-
916 sleOffer->setFlag(lsfSell);
-
917 if (domainID)
-
918 sleOffer->setFieldH256(sfDomainID, *domainID);
+
865 auto setBookDir = [&](SLE::ref sle,
+
866 std::optional<uint256> const& maybeDomain) {
+
867 sle->setFieldH160(sfTakerPaysCurrency, saTakerPays.issue().currency);
+
868 sle->setFieldH160(sfTakerPaysIssuer, saTakerPays.issue().account);
+
869 sle->setFieldH160(sfTakerGetsCurrency, saTakerGets.issue().currency);
+
870 sle->setFieldH160(sfTakerGetsIssuer, saTakerGets.issue().account);
+
871 sle->setFieldU64(sfExchangeRate, uRate);
+
872 if (maybeDomain)
+
873 sle->setFieldH256(sfDomainID, *maybeDomain);
+
874 };
+
875
+
876 auto const bookNode = sb.dirAppend(dir, offer_index, [&](SLE::ref sle) {
+
877 // sets domainID on book directory if it's a domain offer
+
878 setBookDir(sle, domainID);
+
879 });
+
880
+
881 if (!bookNode)
+
882 {
+
883 // LCOV_EXCL_START
+
884 JLOG(j_.debug()) << "final result: failed to add offer to book";
+
885 return {tecDIR_FULL, true};
+
886 // LCOV_EXCL_STOP
+
887 }
+
888
+
889 auto sleOffer = std::make_shared<SLE>(offer_index);
+
890 sleOffer->setAccountID(sfAccount, account_);
+
891 sleOffer->setFieldU32(sfSequence, offerSequence);
+
892 sleOffer->setFieldH256(sfBookDirectory, dir.key);
+
893 sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
+
894 sleOffer->setFieldAmount(sfTakerGets, saTakerGets);
+
895 sleOffer->setFieldU64(sfOwnerNode, *ownerNode);
+
896 sleOffer->setFieldU64(sfBookNode, *bookNode);
+
897 if (expiration)
+
898 sleOffer->setFieldU32(sfExpiration, *expiration);
+
899 if (bPassive)
+
900 sleOffer->setFlag(lsfPassive);
+
901 if (bSell)
+
902 sleOffer->setFlag(lsfSell);
+
903 if (domainID)
+
904 sleOffer->setFieldH256(sfDomainID, *domainID);
+
905
+
906 // if it's a hybrid offer, set hybrid flag, and create an open dir
+
907 if (bHybrid)
+
908 {
+
909 auto const res = applyHybrid(
+
910 sb, sleOffer, offer_index, saTakerPays, saTakerGets, setBookDir);
+
911 if (res != tesSUCCESS)
+
912 return {res, true}; // LCOV_EXCL_LINE
+
913 }
+
914
+
915 sb.insert(sleOffer);
+
916
+
917 if (!bookExisted)
+
919
-
920 // if it's a hybrid offer, set hybrid flag, and create an open dir
-
921 if (bHybrid)
-
922 {
-
923 auto const res = applyHybrid(
-
924 sb, sleOffer, offer_index, saTakerPays, saTakerGets, setBookDir);
-
925 if (res != tesSUCCESS)
-
926 return {res, true}; // LCOV_EXCL_LINE
-
927 }
-
928
-
929 sb.insert(sleOffer);
-
930
-
931 if (!bookExisted)
- -
933
-
934 JLOG(j_.debug()) << "final result: success";
-
935
-
936 return {tesSUCCESS, true};
-
937}
+
920 JLOG(j_.debug()) << "final result: success";
+
921
+
922 return {tesSUCCESS, true};
+
923}
-
938
-
939TER
-
- -
941{
-
942 // This is the ledger view that we work against. Transactions are applied
-
943 // as we go on processing transactions.
-
944 Sandbox sb(&ctx_.view());
-
945
-
946 // This is a ledger with just the fees paid and any unfunded or expired
-
947 // offers we encounter removed. It's used when handling Fill-or-Kill offers,
-
948 // if the order isn't going to be placed, to avoid wasting the work we did.
-
949 Sandbox sbCancel(&ctx_.view());
-
950
-
951 auto const result = applyGuts(sb, sbCancel);
-
952 if (result.second)
-
953 sb.apply(ctx_.rawView());
-
954 else
-
955 sbCancel.apply(ctx_.rawView());
-
956 return result.first;
-
957}
+
924
+
925TER
+
+ +
927{
+
928 // This is the ledger view that we work against. Transactions are applied
+
929 // as we go on processing transactions.
+
930 Sandbox sb(&ctx_.view());
+
931
+
932 // This is a ledger with just the fees paid and any unfunded or expired
+
933 // offers we encounter removed. It's used when handling Fill-or-Kill offers,
+
934 // if the order isn't going to be placed, to avoid wasting the work we did.
+
935 Sandbox sbCancel(&ctx_.view());
+
936
+
937 auto const result = applyGuts(sb, sbCancel);
+
938 if (result.second)
+
939 sb.apply(ctx_.rawView());
+
940 else
+
941 sbCancel.apply(ctx_.rawView());
+
942 return result.first;
+
943}
-
958
-
959} // namespace ripple
+
944
+
945} // namespace ripple
A generic endpoint for log messages.
Definition Journal.h:60
Stream fatal() const
Definition Journal.h:352
@@ -1080,14 +1066,14 @@ $(document).ready(function() { init_codefold(0); });
std::pair< TER, Amounts > flowCross(PaymentSandbox &psb, PaymentSandbox &psbCancel, Amounts const &takerAmount, std::optional< uint256 > const &domainID)
static TER checkAcceptAsset(ReadView const &view, ApplyFlags const flags, AccountID const id, beast::Journal const j, Issue const &issue)
static TER preclaim(PreclaimContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
-
static std::string format_amount(STAmount const &amount)
+
static std::string format_amount(STAmount const &amount)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
-
TER applyHybrid(Sandbox &sb, std::shared_ptr< STLedgerEntry > sleOffer, Keylet const &offer_index, STAmount const &saTakerPays, STAmount const &saTakerGets, std::function< void(SLE::ref, std::optional< uint256 >)> const &setDir)
-
TER doApply() override
Precondition: fee collection is likely.
+
TER applyHybrid(Sandbox &sb, std::shared_ptr< STLedgerEntry > sleOffer, Keylet const &offer_index, STAmount const &saTakerPays, STAmount const &saTakerGets, std::function< void(SLE::ref, std::optional< uint256 >)> const &setDir)
+
TER doApply() override
Precondition: fee collection is likely.
static bool checkExtraFeatures(PreflightContext const &ctx)
-
std::pair< TER, bool > applyGuts(Sandbox &view, Sandbox &view_cancel)
+
std::pair< TER, bool > applyGuts(Sandbox &view, Sandbox &view_cancel)
A currency issued by an account.
Definition Issue.h:33
AccountID account
Definition Issue.h:36
Currency currency
Definition Issue.h:35
@@ -1198,7 +1184,6 @@ $(document).ready(function() { init_codefold(0); });
@ tecNO_AUTH
Definition TER.h:301
@ tesSUCCESS
Definition TER.h:245
bool isTesSuccess(TER x) noexcept
Definition TER.h:678
-
STAmount divRound(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
STAmount mulRound(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
STAmount multiplyRound(STAmount const &amount, Rate const &rate, bool roundUp)
Definition Rate2.cpp:64
diff --git a/CreateOffer_8h_source.html b/CreateOffer_8h_source.html index 75cc6685f9..f1f810ca5b 100644 --- a/CreateOffer_8h_source.html +++ b/CreateOffer_8h_source.html @@ -190,15 +190,15 @@ $(document).ready(function() { init_codefold(0); });
static TER checkAcceptAsset(ReadView const &view, ApplyFlags const flags, AccountID const id, beast::Journal const j, Issue const &issue)
CreateOffer(ApplyContext &ctx)
Construct a Transactor subclass that creates an offer in the ledger.
Definition CreateOffer.h:39
static TER preclaim(PreclaimContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
-
static std::string format_amount(STAmount const &amount)
+
static std::string format_amount(STAmount const &amount)
static constexpr ConsequencesFactoryType ConsequencesFactory
Definition CreateOffer.h:36
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
-
TER applyHybrid(Sandbox &sb, std::shared_ptr< STLedgerEntry > sleOffer, Keylet const &offer_index, STAmount const &saTakerPays, STAmount const &saTakerGets, std::function< void(SLE::ref, std::optional< uint256 >)> const &setDir)
-
TER doApply() override
Precondition: fee collection is likely.
+
TER applyHybrid(Sandbox &sb, std::shared_ptr< STLedgerEntry > sleOffer, Keylet const &offer_index, STAmount const &saTakerPays, STAmount const &saTakerGets, std::function< void(SLE::ref, std::optional< uint256 >)> const &setDir)
+
TER doApply() override
Precondition: fee collection is likely.
static bool checkExtraFeatures(PreflightContext const &ctx)
-
std::pair< TER, bool > applyGuts(Sandbox &view, Sandbox &view_cancel)
+
std::pair< TER, bool > applyGuts(Sandbox &view, Sandbox &view_cancel)
A currency issued by an account.
Definition Issue.h:33
A wrapper which makes credits unavailable to balances.
A view into a ledger.
Definition ReadView.h:51
diff --git a/OfferStream_8cpp_source.html b/OfferStream_8cpp_source.html index 7c6905c1f1..cd11d9858e 100644 --- a/OfferStream_8cpp_source.html +++ b/OfferStream_8cpp_source.html @@ -280,261 +280,253 @@ $(document).ready(function() { init_codefold(0); });
184 }
185
186 TTakerGets const ownerFunds = toAmount<TTakerGets>(*ownerFunds_);
-
187 bool const fixReduced = view_.rules().enabled(fixReducedOffersV1);
-
188
-
189 auto const effectiveAmounts = [&] {
-
190 if (offer_.owner() != offer_.issueOut().account &&
-
191 ownerFunds < ofrAmts.out)
-
192 {
-
193 // adjust the amounts by owner funds.
-
194 //
-
195 // It turns out we can prevent order book blocking by rounding down
-
196 // the ceil_out() result. This adjustment changes transaction
-
197 // results, so it must be made under an amendment.
-
198 if (fixReduced)
-
199 return offer_.quality().ceil_out_strict(
-
200 ofrAmts, ownerFunds, /* roundUp */ false);
+
187
+
188 auto const effectiveAmounts = [&] {
+
189 if (offer_.owner() != offer_.issueOut().account &&
+
190 ownerFunds < ofrAmts.out)
+
191 {
+
192 // adjust the amounts by owner funds.
+
193 //
+
194 // It turns out we can prevent order book blocking by rounding down
+
195 // the ceil_out() result.
+
196 return offer_.quality().ceil_out_strict(
+
197 ofrAmts, ownerFunds, /* roundUp */ false);
+
198 }
+
199 return ofrAmts;
+
200 }();
201
-
202 return offer_.quality().ceil_out(ofrAmts, ownerFunds);
-
203 }
-
204 return ofrAmts;
-
205 }();
-
206
-
207 // If either the effective in or out are zero then remove the offer.
-
208 // This can happen with fixReducedOffersV1 since it rounds down.
-
209 if (fixReduced &&
-
210 (effectiveAmounts.in.signum() <= 0 ||
-
211 effectiveAmounts.out.signum() <= 0))
-
212 return true;
-
213
-
214 if (effectiveAmounts.in > TTakerPays::minPositiveAmount())
-
215 return false;
-
216
-
217 Quality const effectiveQuality{effectiveAmounts};
-
218 return effectiveQuality < offer_.quality();
-
219}
+
202 // If either the effective in or out are zero then remove the offer.
+
203 if (effectiveAmounts.in.signum() <= 0 || effectiveAmounts.out.signum() <= 0)
+
204 return true;
+
205
+
206 if (effectiveAmounts.in > TTakerPays::minPositiveAmount())
+
207 return false;
+
208
+
209 Quality const effectiveQuality{effectiveAmounts};
+
210 return effectiveQuality < offer_.quality();
+
211}
-
220
-
221template <class TIn, class TOut>
-
222bool
-
- -
224{
-
225 // Modifying the order or logic of these
-
226 // operations causes a protocol breaking change.
-
227
-
228 if (!validBook_)
-
229 return false;
+
212
+
213template <class TIn, class TOut>
+
214bool
+
+ +
216{
+
217 // Modifying the order or logic of these
+
218 // operations causes a protocol breaking change.
+
219
+
220 if (!validBook_)
+
221 return false;
+
222
+
223 for (;;)
+
224 {
+
225 ownerFunds_ = std::nullopt;
+
226 // BookTip::step deletes the current offer from the view before
+
227 // advancing to the next (unless the ledger entry is missing).
+
228 if (!tip_.step(j_))
+
229 return false;
230
-
231 for (;;)
-
232 {
-
233 ownerFunds_ = std::nullopt;
-
234 // BookTip::step deletes the current offer from the view before
-
235 // advancing to the next (unless the ledger entry is missing).
-
236 if (!tip_.step(j_))
-
237 return false;
-
238
-
239 std::shared_ptr<SLE> entry = tip_.entry();
-
240
-
241 // If we exceed the maximum number of allowed steps, we're done.
-
242 if (!counter_.step())
-
243 return false;
+
231 std::shared_ptr<SLE> entry = tip_.entry();
+
232
+
233 // If we exceed the maximum number of allowed steps, we're done.
+
234 if (!counter_.step())
+
235 return false;
+
236
+
237 // Remove if missing
+
238 if (!entry)
+
239 {
+
240 erase(view_);
+
241 erase(cancelView_);
+
242 continue;
+
243 }
244
-
245 // Remove if missing
-
246 if (!entry)
-
247 {
-
248 erase(view_);
-
249 erase(cancelView_);
-
250 continue;
-
251 }
-
252
-
253 // Remove if expired
-
254 using d = NetClock::duration;
-
255 using tp = NetClock::time_point;
-
256 if (entry->isFieldPresent(sfExpiration) &&
-
257 tp{d{(*entry)[sfExpiration]}} <= expire_)
-
258 {
-
259 JLOG(j_.trace()) << "Removing expired offer " << entry->key();
-
260 permRmOffer(entry->key());
-
261 continue;
-
262 }
-
263
-
264 offer_ = TOffer<TIn, TOut>(entry, tip_.quality());
-
265
-
266 auto const amount(offer_.amount());
-
267
-
268 // Remove if either amount is zero
-
269 if (amount.empty())
-
270 {
-
271 JLOG(j_.warn()) << "Removing bad offer " << entry->key();
-
272 permRmOffer(entry->key());
-
273 offer_ = TOffer<TIn, TOut>{};
-
274 continue;
-
275 }
-
276
-
277 bool const deepFrozen = isDeepFrozen(
-
278 view_,
-
279 offer_.owner(),
-
280 offer_.issueIn().currency,
-
281 offer_.issueIn().account);
-
282 if (deepFrozen)
-
283 {
-
284 JLOG(j_.trace())
-
285 << "Removing deep frozen unfunded offer " << entry->key();
-
286 permRmOffer(entry->key());
-
287 offer_ = TOffer<TIn, TOut>{};
-
288 continue;
-
289 }
-
290
-
291 if (entry->isFieldPresent(sfDomainID) &&
- -
293 view_, entry->key(), entry->getFieldH256(sfDomainID), j_))
-
294 {
-
295 JLOG(j_.trace())
-
296 << "Removing offer no longer in domain " << entry->key();
-
297 permRmOffer(entry->key());
-
298 offer_ = TOffer<TIn, TOut>{};
-
299 continue;
-
300 }
-
301
-
302 // Calculate owner funds
-
303 ownerFunds_ = accountFundsHelper(
-
304 view_,
-
305 offer_.owner(),
-
306 amount.out,
-
307 offer_.issueOut(),
- -
309 j_);
-
310
-
311 // Check for unfunded offer
-
312 if (*ownerFunds_ <= beast::zero)
-
313 {
-
314 // If the owner's balance in the pristine view is the same,
-
315 // we haven't modified the balance and therefore the
-
316 // offer is "found unfunded" versus "became unfunded"
-
317 auto const original_funds = accountFundsHelper(
-
318 cancelView_,
-
319 offer_.owner(),
-
320 amount.out,
-
321 offer_.issueOut(),
- -
323 j_);
-
324
-
325 if (original_funds == *ownerFunds_)
-
326 {
-
327 permRmOffer(entry->key());
-
328 JLOG(j_.trace()) << "Removing unfunded offer " << entry->key();
-
329 }
-
330 else
-
331 {
-
332 JLOG(j_.trace())
-
333 << "Removing became unfunded offer " << entry->key();
-
334 }
-
335 offer_ = TOffer<TIn, TOut>{};
-
336 // See comment at top of loop for how the offer is removed
-
337 continue;
-
338 }
-
339
-
340 bool const rmSmallIncreasedQOffer = [&] {
-
341 bool const inIsXRP = isXRP(offer_.issueIn());
-
342 bool const outIsXRP = isXRP(offer_.issueOut());
-
343 if (inIsXRP && !outIsXRP)
-
344 {
-
345 // Without the `if constexpr`, the
-
346 // `shouldRmSmallIncreasedQOffer` template will be instantiated
-
347 // even if it is never used. This can cause compiler errors in
-
348 // some cases, hence the `if constexpr` guard.
-
349 // Note that TIn can be XRPAmount or STAmount, and TOut can be
-
350 // IOUAmount or STAmount.
-
351 if constexpr (!(std::is_same_v<TIn, IOUAmount> ||
- -
353 return shouldRmSmallIncreasedQOffer<XRPAmount, IOUAmount>();
-
354 }
-
355 if (!inIsXRP && outIsXRP)
-
356 {
-
357 // See comment above for `if constexpr` rationale
-
358 if constexpr (!(std::is_same_v<TIn, XRPAmount> ||
- -
360 return shouldRmSmallIncreasedQOffer<IOUAmount, XRPAmount>();
-
361 }
-
362 if (!inIsXRP && !outIsXRP)
-
363 {
-
364 // See comment above for `if constexpr` rationale
-
365 if constexpr (!(std::is_same_v<TIn, XRPAmount> ||
- -
367 return shouldRmSmallIncreasedQOffer<IOUAmount, IOUAmount>();
-
368 }
-
369 // LCOV_EXCL_START
-
370 UNREACHABLE(
-
371 "rippls::TOfferStreamBase::step::rmSmallIncreasedQOffer : XRP "
-
372 "vs XRP offer");
-
373 return false;
-
374 // LCOV_EXCL_STOP
-
375 }();
-
376
-
377 if (rmSmallIncreasedQOffer)
-
378 {
-
379 auto const original_funds = accountFundsHelper(
-
380 cancelView_,
-
381 offer_.owner(),
-
382 amount.out,
-
383 offer_.issueOut(),
- -
385 j_);
-
386
-
387 if (original_funds == *ownerFunds_)
-
388 {
-
389 permRmOffer(entry->key());
-
390 JLOG(j_.trace())
-
391 << "Removing tiny offer due to reduced quality "
-
392 << entry->key();
-
393 }
-
394 else
-
395 {
-
396 JLOG(j_.trace()) << "Removing tiny offer that became tiny due "
-
397 "to reduced quality "
-
398 << entry->key();
-
399 }
-
400 offer_ = TOffer<TIn, TOut>{};
-
401 // See comment at top of loop for how the offer is removed
-
402 continue;
-
403 }
-
404
-
405 break;
-
406 }
-
407
-
408 return true;
-
409}
+
245 // Remove if expired
+
246 using d = NetClock::duration;
+
247 using tp = NetClock::time_point;
+
248 if (entry->isFieldPresent(sfExpiration) &&
+
249 tp{d{(*entry)[sfExpiration]}} <= expire_)
+
250 {
+
251 JLOG(j_.trace()) << "Removing expired offer " << entry->key();
+
252 permRmOffer(entry->key());
+
253 continue;
+
254 }
+
255
+
256 offer_ = TOffer<TIn, TOut>(entry, tip_.quality());
+
257
+
258 auto const amount(offer_.amount());
+
259
+
260 // Remove if either amount is zero
+
261 if (amount.empty())
+
262 {
+
263 JLOG(j_.warn()) << "Removing bad offer " << entry->key();
+
264 permRmOffer(entry->key());
+
265 offer_ = TOffer<TIn, TOut>{};
+
266 continue;
+
267 }
+
268
+
269 bool const deepFrozen = isDeepFrozen(
+
270 view_,
+
271 offer_.owner(),
+
272 offer_.issueIn().currency,
+
273 offer_.issueIn().account);
+
274 if (deepFrozen)
+
275 {
+
276 JLOG(j_.trace())
+
277 << "Removing deep frozen unfunded offer " << entry->key();
+
278 permRmOffer(entry->key());
+
279 offer_ = TOffer<TIn, TOut>{};
+
280 continue;
+
281 }
+
282
+
283 if (entry->isFieldPresent(sfDomainID) &&
+ +
285 view_, entry->key(), entry->getFieldH256(sfDomainID), j_))
+
286 {
+
287 JLOG(j_.trace())
+
288 << "Removing offer no longer in domain " << entry->key();
+
289 permRmOffer(entry->key());
+
290 offer_ = TOffer<TIn, TOut>{};
+
291 continue;
+
292 }
+
293
+
294 // Calculate owner funds
+
295 ownerFunds_ = accountFundsHelper(
+
296 view_,
+
297 offer_.owner(),
+
298 amount.out,
+
299 offer_.issueOut(),
+ +
301 j_);
+
302
+
303 // Check for unfunded offer
+
304 if (*ownerFunds_ <= beast::zero)
+
305 {
+
306 // If the owner's balance in the pristine view is the same,
+
307 // we haven't modified the balance and therefore the
+
308 // offer is "found unfunded" versus "became unfunded"
+
309 auto const original_funds = accountFundsHelper(
+
310 cancelView_,
+
311 offer_.owner(),
+
312 amount.out,
+
313 offer_.issueOut(),
+ +
315 j_);
+
316
+
317 if (original_funds == *ownerFunds_)
+
318 {
+
319 permRmOffer(entry->key());
+
320 JLOG(j_.trace()) << "Removing unfunded offer " << entry->key();
+
321 }
+
322 else
+
323 {
+
324 JLOG(j_.trace())
+
325 << "Removing became unfunded offer " << entry->key();
+
326 }
+
327 offer_ = TOffer<TIn, TOut>{};
+
328 // See comment at top of loop for how the offer is removed
+
329 continue;
+
330 }
+
331
+
332 bool const rmSmallIncreasedQOffer = [&] {
+
333 bool const inIsXRP = isXRP(offer_.issueIn());
+
334 bool const outIsXRP = isXRP(offer_.issueOut());
+
335 if (inIsXRP && !outIsXRP)
+
336 {
+
337 // Without the `if constexpr`, the
+
338 // `shouldRmSmallIncreasedQOffer` template will be instantiated
+
339 // even if it is never used. This can cause compiler errors in
+
340 // some cases, hence the `if constexpr` guard.
+
341 // Note that TIn can be XRPAmount or STAmount, and TOut can be
+
342 // IOUAmount or STAmount.
+
343 if constexpr (!(std::is_same_v<TIn, IOUAmount> ||
+ +
345 return shouldRmSmallIncreasedQOffer<XRPAmount, IOUAmount>();
+
346 }
+
347 if (!inIsXRP && outIsXRP)
+
348 {
+
349 // See comment above for `if constexpr` rationale
+
350 if constexpr (!(std::is_same_v<TIn, XRPAmount> ||
+ +
352 return shouldRmSmallIncreasedQOffer<IOUAmount, XRPAmount>();
+
353 }
+
354 if (!inIsXRP && !outIsXRP)
+
355 {
+
356 // See comment above for `if constexpr` rationale
+
357 if constexpr (!(std::is_same_v<TIn, XRPAmount> ||
+ +
359 return shouldRmSmallIncreasedQOffer<IOUAmount, IOUAmount>();
+
360 }
+
361 // LCOV_EXCL_START
+
362 UNREACHABLE(
+
363 "rippls::TOfferStreamBase::step::rmSmallIncreasedQOffer : XRP "
+
364 "vs XRP offer");
+
365 return false;
+
366 // LCOV_EXCL_STOP
+
367 }();
+
368
+
369 if (rmSmallIncreasedQOffer)
+
370 {
+
371 auto const original_funds = accountFundsHelper(
+
372 cancelView_,
+
373 offer_.owner(),
+
374 amount.out,
+
375 offer_.issueOut(),
+ +
377 j_);
+
378
+
379 if (original_funds == *ownerFunds_)
+
380 {
+
381 permRmOffer(entry->key());
+
382 JLOG(j_.trace())
+
383 << "Removing tiny offer due to reduced quality "
+
384 << entry->key();
+
385 }
+
386 else
+
387 {
+
388 JLOG(j_.trace()) << "Removing tiny offer that became tiny due "
+
389 "to reduced quality "
+
390 << entry->key();
+
391 }
+
392 offer_ = TOffer<TIn, TOut>{};
+
393 // See comment at top of loop for how the offer is removed
+
394 continue;
+
395 }
+
396
+
397 break;
+
398 }
+
399
+
400 return true;
+
401}
-
410
-
411void
-
-
412OfferStream::permRmOffer(uint256 const& offerIndex)
-
413{
-
414 offerDelete(cancelView_, cancelView_.peek(keylet::offer(offerIndex)), j_);
-
415}
+
402
+
403void
+
+
404OfferStream::permRmOffer(uint256 const& offerIndex)
+
405{
+
406 offerDelete(cancelView_, cancelView_.peek(keylet::offer(offerIndex)), j_);
+
407}
-
416
-
417template <class TIn, class TOut>
-
418void
-
- -
420{
-
421 permToRemove_.insert(offerIndex);
-
422}
+
408
+
409template <class TIn, class TOut>
+
410void
+
+ +
412{
+
413 permToRemove_.insert(offerIndex);
+
414}
-
423
- - - - -
428
- - - - -
433} // namespace ripple
+
415
+ + + + +
420
+ + + + +
425} // namespace ripple
@@ -560,7 +552,7 @@ $(document).ready(function() { init_codefold(0); });
void erase(ApplyView &view)
-
bool step()
Advance to the next valid offer.
+
bool step()
Advance to the next valid offer.
TOfferStreamBase(ApplyView &view, ApplyView &cancelView, Book const &book, NetClock::time_point when, StepCounter &counter, beast::Journal journal)
bool shouldRmSmallIncreasedQOffer() const
diff --git a/OfferStream_8h_source.html b/OfferStream_8h_source.html index 94d492a81a..cdb4a99961 100644 --- a/OfferStream_8h_source.html +++ b/OfferStream_8h_source.html @@ -261,10 +261,10 @@ $(document).ready(function() { init_codefold(0); });
Specifies an order book.
Definition Book.h:36
Presents and consumes the offers in an order book.
boost::container::flat_set< uint256 > const & permToRemove() const
-
void permRmOffer(uint256 const &offerIndex) override
+
void permRmOffer(uint256 const &offerIndex) override
boost::container::flat_set< uint256 > permToRemove_
Presents and consumes the offers in an order book.
-
void permRmOffer(uint256 const &offerIndex) override
+
void permRmOffer(uint256 const &offerIndex) override
@@ -276,7 +276,7 @@ $(document).ready(function() { init_codefold(0); });
std::optional< TOut > ownerFunds_
Definition OfferStream.h:79
void erase(ApplyView &view)
-
bool step()
Advance to the next valid offer.
+
bool step()
Advance to the next valid offer.
beast::Journal const j_
Definition OfferStream.h:71
virtual ~TOfferStreamBase()=default
diff --git a/Offer_8h_source.html b/Offer_8h_source.html index e317bd9af9..7a5f2960e5 100644 --- a/Offer_8h_source.html +++ b/Offer_8h_source.html @@ -345,135 +345,130 @@ $(document).ready(function() { init_codefold(0); });
241 TOut const& limit,
242 bool roundUp) const
243{
-
244 if (auto const& rules = getCurrentTransactionRules();
-
245 rules && rules->enabled(fixReducedOffersV1))
-
246 // It turns out that the ceil_out implementation has some slop in
-
247 // it. ceil_out_strict removes that slop. But removing that slop
-
248 // affects transaction outcomes, so the change must be made using
-
249 // an amendment.
-
250 return quality().ceil_out_strict(offrAmt, limit, roundUp);
-
251 return m_quality.ceil_out(offrAmt, limit);
-
252}
+
244 // It turns out that the ceil_out implementation has some slop in
+
245 // it, which ceil_out_strict removes.
+
246 return quality().ceil_out_strict(offrAmt, limit, roundUp);
+
247}
-
253
-
254template <class TIn, class TOut>
-
255TAmounts<TIn, TOut>
-
- -
257 TAmounts<TIn, TOut> const& offrAmt,
-
258 TIn const& limit,
-
259 bool roundUp) const
-
260{
-
261 if (auto const& rules = getCurrentTransactionRules();
-
262 rules && rules->enabled(fixReducedOffersV2))
-
263 // It turns out that the ceil_in implementation has some slop in
-
264 // it. ceil_in_strict removes that slop. But removing that slop
-
265 // affects transaction outcomes, so the change must be made using
-
266 // an amendment.
-
267 return quality().ceil_in_strict(offrAmt, limit, roundUp);
-
268 return m_quality.ceil_in(offrAmt, limit);
-
269}
+
248
+
249template <class TIn, class TOut>
+
250TAmounts<TIn, TOut>
+
+ +
252 TAmounts<TIn, TOut> const& offrAmt,
+
253 TIn const& limit,
+
254 bool roundUp) const
+
255{
+
256 if (auto const& rules = getCurrentTransactionRules();
+
257 rules && rules->enabled(fixReducedOffersV2))
+
258 // It turns out that the ceil_in implementation has some slop in
+
259 // it. ceil_in_strict removes that slop. But removing that slop
+
260 // affects transaction outcomes, so the change must be made using
+
261 // an amendment.
+
262 return quality().ceil_in_strict(offrAmt, limit, roundUp);
+
263 return m_quality.ceil_in(offrAmt, limit);
+
264}
-
270
-
271template <class TIn, class TOut>
-
272template <typename... Args>
-
273TER
-
- -
275{
-
276 return accountSend(std::forward<Args>(args)...);
-
277}
+
265
+
266template <class TIn, class TOut>
+
267template <typename... Args>
+
268TER
+
+ +
270{
+
271 return accountSend(std::forward<Args>(args)...);
+
272}
-
278
-
279template <>
-
280inline void
-
- -
282{
-
283 m_entry->setFieldAmount(sfTakerPays, m_amounts.in);
-
284 m_entry->setFieldAmount(sfTakerGets, m_amounts.out);
-
285}
+
273
+
274template <>
+
275inline void
+
+ +
277{
+
278 m_entry->setFieldAmount(sfTakerPays, m_amounts.in);
+
279 m_entry->setFieldAmount(sfTakerGets, m_amounts.out);
+
280}
-
286
-
287template <>
-
288inline void
-
- -
290{
-
291 m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, issIn_));
-
292 m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, issOut_));
-
293}
+
281
+
282template <>
+
283inline void
+
+ +
285{
+
286 m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, issIn_));
+
287 m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, issOut_));
+
288}
-
294
-
295template <>
-
296inline void
-
- -
298{
-
299 m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, issIn_));
-
300 m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out));
-
301}
+
289
+
290template <>
+
291inline void
+
+ +
293{
+
294 m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, issIn_));
+
295 m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out));
+
296}
-
302
-
303template <>
-
304inline void
-
- -
306{
-
307 m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in));
-
308 m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, issOut_));
-
309}
+
297
+
298template <>
+
299inline void
+
+ +
301{
+
302 m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in));
+
303 m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, issOut_));
+
304}
-
310
-
311template <class TIn, class TOut>
-
312Issue const&
-
- -
314{
-
315 return this->issIn_;
-
316}
+
305
+
306template <class TIn, class TOut>
+
307Issue const&
+
+ +
309{
+
310 return this->issIn_;
+
311}
-
317
-
318template <>
-
319inline Issue const&
-
- -
321{
-
322 return m_amounts.in.issue();
-
323}
+
312
+
313template <>
+
314inline Issue const&
+
+ +
316{
+
317 return m_amounts.in.issue();
+
318}
-
324
-
325template <class TIn, class TOut>
-
326Issue const&
-
- -
328{
-
329 return this->issOut_;
-
330}
+
319
+
320template <class TIn, class TOut>
+
321Issue const&
+
+ +
323{
+
324 return this->issOut_;
+
325}
-
331
-
332template <>
-
333inline Issue const&
-
- -
335{
-
336 return m_amounts.out.issue();
-
337}
+
326
+
327template <>
+
328inline Issue const&
+
+ +
330{
+
331 return m_amounts.out.issue();
+
332}
-
338
-
339template <class TIn, class TOut>
-
- - -
342{
-
343 return os << offer.id();
-
344}
+
333
+
334template <class TIn, class TOut>
+
+ + +
337{
+
338 return os << offer.id();
+
339}
-
345
-
346} // namespace ripple
-
347
-
348#endif
+
340
+
341} // namespace ripple
+
342
+
343#endif
@@ -498,13 +493,13 @@ $(document).ready(function() { init_codefold(0); });
bool fully_consumed() const
Returns true if no more funds can flow through this offer.
Definition Offer.h:100
TOffer()=default
-
Issue const & issueIn() const
Definition Offer.h:313
+
Issue const & issueIn() const
Definition Offer.h:308
TOffer(SLE::pointer const &entry, Quality quality)
Definition Offer.h:198
Quality m_quality
Definition Offer.h:55
SLE::pointer m_entry
Definition Offer.h:54
-
Issue const & issueOut() const
Definition Offer.h:327
+
Issue const & issueOut() const
Definition Offer.h:322
Quality quality() const noexcept
Returns the quality of the offer.
Definition Offer.h:77
-
TAmounts< TIn, TOut > limitIn(TAmounts< TIn, TOut > const &offrAmt, TIn const &limit, bool roundUp) const
Definition Offer.h:256
+
TAmounts< TIn, TOut > limitIn(TAmounts< TIn, TOut > const &offrAmt, TIn const &limit, bool roundUp) const
Definition Offer.h:251
std::string id() const
Definition Offer.h:125
TAmounts< TIn, TOut > limitOut(TAmounts< TIn, TOut > const &offrAmt, TOut const &limit, bool roundUp) const
Definition Offer.h:239
AccountID const & owner() const
Returns the account id of the offer's owner.
Definition Offer.h:84
@@ -513,7 +508,7 @@ $(document).ready(function() { init_codefold(0); });
TAmounts< TIn, TOut > const & amount() const
Returns the in and out amounts.
Definition Offer.h:93
std::optional< uint256 > key() const
Definition Offer.h:131
static std::pair< std::uint32_t, std::uint32_t > adjustRates(std::uint32_t ofrInRate, std::uint32_t ofrOutRate)
Definition Offer.h:163
-
static TER send(Args &&... args)
Definition Offer.h:274
+
static TER send(Args &&... args)
Definition Offer.h:269
void setFieldAmounts()
Definition Offer.h:226
TAmounts< TIn, TOut > m_amounts
Definition Offer.h:58
AccountID m_account
Definition Offer.h:56
diff --git a/ReducedOffer__test_8cpp_source.html b/ReducedOffer__test_8cpp_source.html index c8fb2fb155..f88875f314 100644 --- a/ReducedOffer__test_8cpp_source.html +++ b/ReducedOffer__test_8cpp_source.html @@ -171,744 +171,682 @@ $(document).ready(function() { init_codefold(0); });
80 auto const bob = Account{"bob"};
81 auto const USD = gw["USD"];
82
-
83 // Make one test run without fixReducedOffersV1 and one with.
-
84 for (FeatureBitset features :
-
85 {testable_amendments() - fixReducedOffersV1,
-
86 testable_amendments() | fixReducedOffersV1})
-
87 {
-
88 Env env{*this, features};
+
83 {
+
84 Env env{*this, testable_amendments()};
+
85
+
86 // Make sure none of the offers we generate are under funded.
+
87 env.fund(XRP(10'000'000), gw, alice, bob);
+
88 env.close();
89
-
90 // Make sure none of the offers we generate are under funded.
-
91 env.fund(XRP(10'000'000), gw, alice, bob);
+
90 env(trust(alice, USD(10'000'000)));
+
91 env(trust(bob, USD(10'000'000)));
92 env.close();
93
-
94 env(trust(alice, USD(10'000'000)));
-
95 env(trust(bob, USD(10'000'000)));
-
96 env.close();
-
97
-
98 env(pay(gw, bob, USD(10'000'000)));
-
99 env.close();
-
100
-
101 // Lambda that:
-
102 // 1. Exercises one offer pair,
-
103 // 2. Collects the results, and
-
104 // 3. Cleans up for the next offer pair.
-
105 // Returns 1 if the crossed offer has a bad rate for the book.
-
106 auto exerciseOfferPair =
-
107 [this, &env, &alice, &bob](
-
108 Amounts const& inLedger,
-
109 Amounts const& newOffer) -> unsigned int {
-
110 // Put inLedger offer in the ledger so newOffer can cross it.
-
111 std::uint32_t const aliceOfferSeq = env.seq(alice);
-
112 env(offer(alice, inLedger.in, inLedger.out));
-
113 env.close();
-
114
-
115 // Now alice's offer will partially cross bob's offer.
-
116 STAmount const initialRate = Quality(newOffer).rate();
-
117 std::uint32_t const bobOfferSeq = env.seq(bob);
-
118 STAmount const bobInitialBalance = env.balance(bob);
-
119 STAmount const bobsFee = env.current()->fees().base;
-
120 env(offer(bob, newOffer.in, newOffer.out, tfSell),
-
121 fee(bobsFee));
-
122 env.close();
-
123 STAmount const bobFinalBalance = env.balance(bob);
-
124
-
125 // alice's offer should be fully crossed and so gone from
-
126 // the ledger.
-
127 if (!BEAST_EXPECT(!offerInLedger(env, alice, aliceOfferSeq)))
-
128 // If the in-ledger offer was not consumed then further
-
129 // results are meaningless.
-
130 return 1;
-
131
-
132 // bob's offer should be in the ledger, but reduced in size.
-
133 unsigned int badRate = 1;
-
134 {
-
135 Json::Value bobOffer =
-
136 ledgerEntryOffer(env, bob, bobOfferSeq);
-
137
-
138 STAmount const reducedTakerGets = amountFromJson(
-
139 sfTakerGets, bobOffer[jss::node][sfTakerGets.jsonName]);
-
140 STAmount const reducedTakerPays = amountFromJson(
-
141 sfTakerPays, bobOffer[jss::node][sfTakerPays.jsonName]);
-
142 STAmount const bobGot =
-
143 env.balance(bob) + bobsFee - bobInitialBalance;
-
144 BEAST_EXPECT(reducedTakerPays < newOffer.in);
-
145 BEAST_EXPECT(reducedTakerGets < newOffer.out);
-
146 STAmount const inLedgerRate =
-
147 Quality(Amounts{reducedTakerPays, reducedTakerGets})
-
148 .rate();
-
149
-
150 badRate = inLedgerRate > initialRate ? 1 : 0;
-
151
-
152 // If the inLedgerRate is less than initial rate, then
-
153 // incrementing the mantissa of the reduced taker pays
-
154 // should result in a rate higher than initial. Check
-
155 // this to verify that the largest allowable TakerPays
-
156 // was computed.
-
157 if (badRate == 0)
-
158 {
-
159 STAmount const tweakedTakerPays =
-
160 reducedTakerPays + drops(1);
-
161 STAmount const tweakedRate =
-
162 Quality(Amounts{tweakedTakerPays, reducedTakerGets})
-
163 .rate();
-
164 BEAST_EXPECT(tweakedRate > initialRate);
-
165 }
-
166#if 0
-
167 std::cout << "Placed rate: " << initialRate
-
168 << "; in-ledger rate: " << inLedgerRate
-
169 << "; TakerPays: " << reducedTakerPays
-
170 << "; TakerGets: " << reducedTakerGets
-
171 << "; bob already got: " << bobGot << std::endl;
-
172// #else
-
173 std::string_view filler =
-
174 inLedgerRate > initialRate ? "**" : " ";
-
175 std::cout << "| `" << reducedTakerGets << "` | `"
-
176 << reducedTakerPays << "` | `" << initialRate
-
177 << "` | " << filler << "`" << inLedgerRate << "`"
-
178 << filler << " |`" << std::endl;
-
179#endif
-
180 }
-
181
-
182 // In preparation for the next iteration make sure the two
-
183 // offers are gone from the ledger.
- -
185 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
-
186 return badRate;
-
187 };
+
94 env(pay(gw, bob, USD(10'000'000)));
+
95 env.close();
+
96
+
97 // Lambda that:
+
98 // 1. Exercises one offer pair,
+
99 // 2. Collects the results, and
+
100 // 3. Cleans up for the next offer pair.
+
101 // Returns 1 if the crossed offer has a bad rate for the book.
+
102 auto exerciseOfferPair =
+
103 [this, &env, &alice, &bob](
+
104 Amounts const& inLedger,
+
105 Amounts const& newOffer) -> unsigned int {
+
106 // Put inLedger offer in the ledger so newOffer can cross it.
+
107 std::uint32_t const aliceOfferSeq = env.seq(alice);
+
108 env(offer(alice, inLedger.in, inLedger.out));
+
109 env.close();
+
110
+
111 // Now alice's offer will partially cross bob's offer.
+
112 STAmount const initialRate = Quality(newOffer).rate();
+
113 std::uint32_t const bobOfferSeq = env.seq(bob);
+
114 STAmount const bobInitialBalance = env.balance(bob);
+
115 STAmount const bobsFee = env.current()->fees().base;
+
116 env(offer(bob, newOffer.in, newOffer.out, tfSell),
+
117 fee(bobsFee));
+
118 env.close();
+
119 STAmount const bobFinalBalance = env.balance(bob);
+
120
+
121 // alice's offer should be fully crossed and so gone from
+
122 // the ledger.
+
123 if (!BEAST_EXPECT(!offerInLedger(env, alice, aliceOfferSeq)))
+
124 // If the in-ledger offer was not consumed then further
+
125 // results are meaningless.
+
126 return 1;
+
127
+
128 // bob's offer should be in the ledger, but reduced in size.
+
129 unsigned int badRate = 1;
+
130 {
+
131 Json::Value bobOffer =
+
132 ledgerEntryOffer(env, bob, bobOfferSeq);
+
133
+
134 STAmount const reducedTakerGets = amountFromJson(
+
135 sfTakerGets, bobOffer[jss::node][sfTakerGets.jsonName]);
+
136 STAmount const reducedTakerPays = amountFromJson(
+
137 sfTakerPays, bobOffer[jss::node][sfTakerPays.jsonName]);
+
138 STAmount const bobGot =
+
139 env.balance(bob) + bobsFee - bobInitialBalance;
+
140 BEAST_EXPECT(reducedTakerPays < newOffer.in);
+
141 BEAST_EXPECT(reducedTakerGets < newOffer.out);
+
142 STAmount const inLedgerRate =
+
143 Quality(Amounts{reducedTakerPays, reducedTakerGets})
+
144 .rate();
+
145
+
146 badRate = inLedgerRate > initialRate ? 1 : 0;
+
147
+
148 // If the inLedgerRate is less than initial rate, then
+
149 // incrementing the mantissa of the reduced taker pays
+
150 // should result in a rate higher than initial. Check
+
151 // this to verify that the largest allowable TakerPays
+
152 // was computed.
+
153 if (badRate == 0)
+
154 {
+
155 STAmount const tweakedTakerPays =
+
156 reducedTakerPays + drops(1);
+
157 STAmount const tweakedRate =
+
158 Quality(Amounts{tweakedTakerPays, reducedTakerGets})
+
159 .rate();
+
160 BEAST_EXPECT(tweakedRate > initialRate);
+
161 }
+
162#if 0
+
163 std::cout << "Placed rate: " << initialRate
+
164 << "; in-ledger rate: " << inLedgerRate
+
165 << "; TakerPays: " << reducedTakerPays
+
166 << "; TakerGets: " << reducedTakerGets
+
167 << "; bob already got: " << bobGot << std::endl;
+
168// #else
+
169 std::string_view filler =
+
170 inLedgerRate > initialRate ? "**" : " ";
+
171 std::cout << "| `" << reducedTakerGets << "` | `"
+
172 << reducedTakerPays << "` | `" << initialRate
+
173 << "` | " << filler << "`" << inLedgerRate << "`"
+
174 << filler << " |`" << std::endl;
+
175#endif
+
176 }
+
177
+
178 // In preparation for the next iteration make sure the two
+
179 // offers are gone from the ledger.
+ +
181 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
+
182 return badRate;
+
183 };
+
184
+
185 // bob's offer (the new offer) is the same every time:
+
186 Amounts const bobsOffer{
+
187 STAmount(XRP(1)), STAmount(USD.issue(), 1, 0)};
188
-
189 // bob's offer (the new offer) is the same every time:
-
190 Amounts const bobsOffer{
-
191 STAmount(XRP(1)), STAmount(USD.issue(), 1, 0)};
-
192
-
193 // alice's offer has a slightly smaller TakerPays with each
-
194 // iteration. This should mean that the size of the offer bob
-
195 // places in the ledger should increase with each iteration.
-
196 unsigned int blockedCount = 0;
-
197 for (std::uint64_t mantissaReduce = 1'000'000'000ull;
-
198 mantissaReduce <= 5'000'000'000ull;
-
199 mantissaReduce += 20'000'000ull)
-
200 {
-
201 STAmount aliceUSD{
-
202 bobsOffer.out.issue(),
-
203 bobsOffer.out.mantissa() - mantissaReduce,
-
204 bobsOffer.out.exponent()};
-
205 STAmount aliceXRP{
-
206 bobsOffer.in.issue(), bobsOffer.in.mantissa() - 1};
-
207 Amounts alicesOffer{aliceUSD, aliceXRP};
-
208 blockedCount += exerciseOfferPair(alicesOffer, bobsOffer);
-
209 }
-
210
-
211 // If fixReducedOffersV1 is enabled, then none of the test cases
-
212 // should produce a potentially blocking rate.
-
213 //
-
214 // Also verify that if fixReducedOffersV1 is not enabled then
-
215 // some of the test cases produced a potentially blocking rate.
-
216 if (features[fixReducedOffersV1])
-
217 {
-
218 BEAST_EXPECT(blockedCount == 0);
-
219 }
-
220 else
-
221 {
-
222 BEAST_EXPECT(blockedCount >= 170);
-
223 }
-
224 }
-
225 }
+
189 // alice's offer has a slightly smaller TakerPays with each
+
190 // iteration. This should mean that the size of the offer bob
+
191 // places in the ledger should increase with each iteration.
+
192 unsigned int blockedCount = 0;
+
193 for (std::uint64_t mantissaReduce = 1'000'000'000ull;
+
194 mantissaReduce <= 5'000'000'000ull;
+
195 mantissaReduce += 20'000'000ull)
+
196 {
+
197 STAmount aliceUSD{
+
198 bobsOffer.out.issue(),
+
199 bobsOffer.out.mantissa() - mantissaReduce,
+
200 bobsOffer.out.exponent()};
+
201 STAmount aliceXRP{
+
202 bobsOffer.in.issue(), bobsOffer.in.mantissa() - 1};
+
203 Amounts alicesOffer{aliceUSD, aliceXRP};
+
204 blockedCount += exerciseOfferPair(alicesOffer, bobsOffer);
+
205 }
+
206
+
207 // None of the test cases should produce a potentially blocking
+
208 // rate.
+
209 BEAST_EXPECT(blockedCount == 0);
+
210 }
+
211 }
-
226
-
227 void
-
- -
229 {
-
230 testcase("exercise partial cross old XRP/IOU offer Q change");
-
231
-
232 using namespace jtx;
-
233
-
234 auto const gw = Account{"gateway"};
-
235 auto const alice = Account{"alice"};
-
236 auto const bob = Account{"bob"};
-
237 auto const USD = gw["USD"];
-
238
-
239 // Make one test run without fixReducedOffersV1 and one with.
-
240 for (FeatureBitset features :
-
241 {testable_amendments() - fixReducedOffersV1,
-
242 testable_amendments() | fixReducedOffersV1})
-
243 {
-
244 // Make sure none of the offers we generate are under funded.
-
245 Env env{*this, features};
-
246 env.fund(XRP(10'000'000), gw, alice, bob);
-
247 env.close();
-
248
-
249 env(trust(alice, USD(10'000'000)));
-
250 env(trust(bob, USD(10'000'000)));
-
251 env.close();
+
212
+
213 void
+
+ +
215 {
+
216 testcase("exercise partial cross old XRP/IOU offer Q change");
+
217
+
218 using namespace jtx;
+
219
+
220 auto const gw = Account{"gateway"};
+
221 auto const alice = Account{"alice"};
+
222 auto const bob = Account{"bob"};
+
223 auto const USD = gw["USD"];
+
224
+
225 {
+
226 // Make sure none of the offers we generate are under funded.
+
227 Env env{*this, testable_amendments()};
+
228 env.fund(XRP(10'000'000), gw, alice, bob);
+
229 env.close();
+
230
+
231 env(trust(alice, USD(10'000'000)));
+
232 env(trust(bob, USD(10'000'000)));
+
233 env.close();
+
234
+
235 env(pay(gw, alice, USD(10'000'000)));
+
236 env.close();
+
237
+
238 // Lambda that:
+
239 // 1. Exercises one offer pair,
+
240 // 2. Collects the results, and
+
241 // 3. Cleans up for the next offer pair.
+
242 auto exerciseOfferPair =
+
243 [this, &env, &alice, &bob](
+
244 Amounts const& inLedger,
+
245 Amounts const& newOffer) -> unsigned int {
+
246 // Get the inLedger offer into the ledger so newOffer can cross
+
247 // it.
+
248 STAmount const initialRate = Quality(inLedger).rate();
+
249 std::uint32_t const aliceOfferSeq = env.seq(alice);
+
250 env(offer(alice, inLedger.in, inLedger.out));
+
251 env.close();
252
-
253 env(pay(gw, alice, USD(10'000'000)));
-
254 env.close();
-
255
-
256 // Lambda that:
-
257 // 1. Exercises one offer pair,
-
258 // 2. Collects the results, and
-
259 // 3. Cleans up for the next offer pair.
-
260 auto exerciseOfferPair =
-
261 [this, &env, &alice, &bob](
-
262 Amounts const& inLedger,
-
263 Amounts const& newOffer) -> unsigned int {
-
264 // Get the inLedger offer into the ledger so newOffer can cross
-
265 // it.
-
266 STAmount const initialRate = Quality(inLedger).rate();
-
267 std::uint32_t const aliceOfferSeq = env.seq(alice);
-
268 env(offer(alice, inLedger.in, inLedger.out));
-
269 env.close();
-
270
-
271 // Now bob's offer will partially cross alice's offer.
-
272 std::uint32_t const bobOfferSeq = env.seq(bob);
-
273 STAmount const aliceInitialBalance = env.balance(alice);
-
274 env(offer(bob, newOffer.in, newOffer.out));
-
275 env.close();
-
276 STAmount const aliceFinalBalance = env.balance(alice);
-
277
-
278 // bob's offer should not have made it into the ledger.
-
279 if (!BEAST_EXPECT(!offerInLedger(env, bob, bobOfferSeq)))
-
280 {
-
281 // If the in-ledger offer was not consumed then further
-
282 // results are meaningless.
- -
284 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
-
285 return 1;
-
286 }
-
287 // alice's offer should still be in the ledger, but reduced in
-
288 // size.
-
289 unsigned int badRate = 1;
-
290 {
-
291 Json::Value aliceOffer =
-
292 ledgerEntryOffer(env, alice, aliceOfferSeq);
-
293
-
294 STAmount const reducedTakerGets = amountFromJson(
-
295 sfTakerGets,
-
296 aliceOffer[jss::node][sfTakerGets.jsonName]);
-
297 STAmount const reducedTakerPays = amountFromJson(
-
298 sfTakerPays,
-
299 aliceOffer[jss::node][sfTakerPays.jsonName]);
-
300 STAmount const aliceGot =
-
301 env.balance(alice) - aliceInitialBalance;
-
302 BEAST_EXPECT(reducedTakerPays < inLedger.in);
-
303 BEAST_EXPECT(reducedTakerGets < inLedger.out);
-
304 STAmount const inLedgerRate =
-
305 Quality(Amounts{reducedTakerPays, reducedTakerGets})
-
306 .rate();
-
307 badRate = inLedgerRate > initialRate ? 1 : 0;
-
308
-
309 // If the inLedgerRate is less than initial rate, then
-
310 // incrementing the mantissa of the reduced taker pays
-
311 // should result in a rate higher than initial. Check
-
312 // this to verify that the largest allowable TakerPays
-
313 // was computed.
-
314 if (badRate == 0)
-
315 {
-
316 STAmount const tweakedTakerPays =
-
317 reducedTakerPays + drops(1);
-
318 STAmount const tweakedRate =
-
319 Quality(Amounts{tweakedTakerPays, reducedTakerGets})
-
320 .rate();
-
321 BEAST_EXPECT(tweakedRate > initialRate);
-
322 }
-
323#if 0
-
324 std::cout << "Placed rate: " << initialRate
-
325 << "; in-ledger rate: " << inLedgerRate
-
326 << "; TakerPays: " << reducedTakerPays
-
327 << "; TakerGets: " << reducedTakerGets
-
328 << "; alice already got: " << aliceGot
-
329 << std::endl;
-
330// #else
-
331 std::string_view filler = badRate ? "**" : " ";
-
332 std::cout << "| `" << reducedTakerGets << "` | `"
-
333 << reducedTakerPays << "` | `" << initialRate
-
334 << "` | " << filler << "`" << inLedgerRate << "`"
-
335 << filler << " | `" << aliceGot << "` |"
-
336 << std::endl;
-
337#endif
-
338 }
-
339
-
340 // In preparation for the next iteration make sure the two
-
341 // offers are gone from the ledger.
- -
343 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
-
344 return badRate;
-
345 };
-
346
-
347 // alice's offer (the old offer) is the same every time:
-
348 Amounts const aliceOffer{
-
349 STAmount(XRP(1)), STAmount(USD.issue(), 1, 0)};
-
350
-
351 // bob's offer has a slightly smaller TakerPays with each iteration.
-
352 // This should mean that the size of the offer alice leaves in the
-
353 // ledger should increase with each iteration.
-
354 unsigned int blockedCount = 0;
-
355 for (std::uint64_t mantissaReduce = 1'000'000'000ull;
-
356 mantissaReduce <= 4'000'000'000ull;
-
357 mantissaReduce += 20'000'000ull)
-
358 {
-
359 STAmount bobUSD{
-
360 aliceOffer.out.issue(),
-
361 aliceOffer.out.mantissa() - mantissaReduce,
-
362 aliceOffer.out.exponent()};
-
363 STAmount bobXRP{
-
364 aliceOffer.in.issue(), aliceOffer.in.mantissa() - 1};
-
365 Amounts bobsOffer{bobUSD, bobXRP};
-
366
-
367 blockedCount += exerciseOfferPair(aliceOffer, bobsOffer);
-
368 }
-
369
-
370 // If fixReducedOffersV1 is enabled, then none of the test cases
-
371 // should produce a potentially blocking rate.
-
372 //
-
373 // Also verify that if fixReducedOffersV1 is not enabled then
-
374 // some of the test cases produced a potentially blocking rate.
-
375 if (features[fixReducedOffersV1])
-
376 {
-
377 BEAST_EXPECT(blockedCount == 0);
-
378 }
-
379 else
-
380 {
-
381 BEAST_EXPECT(blockedCount > 10);
-
382 }
-
383 }
-
384 }
+
253 // Now bob's offer will partially cross alice's offer.
+
254 std::uint32_t const bobOfferSeq = env.seq(bob);
+
255 STAmount const aliceInitialBalance = env.balance(alice);
+
256 env(offer(bob, newOffer.in, newOffer.out));
+
257 env.close();
+
258 STAmount const aliceFinalBalance = env.balance(alice);
+
259
+
260 // bob's offer should not have made it into the ledger.
+
261 if (!BEAST_EXPECT(!offerInLedger(env, bob, bobOfferSeq)))
+
262 {
+
263 // If the in-ledger offer was not consumed then further
+
264 // results are meaningless.
+ +
266 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
+
267 return 1;
+
268 }
+
269 // alice's offer should still be in the ledger, but reduced in
+
270 // size.
+
271 unsigned int badRate = 1;
+
272 {
+
273 Json::Value aliceOffer =
+
274 ledgerEntryOffer(env, alice, aliceOfferSeq);
+
275
+
276 STAmount const reducedTakerGets = amountFromJson(
+
277 sfTakerGets,
+
278 aliceOffer[jss::node][sfTakerGets.jsonName]);
+
279 STAmount const reducedTakerPays = amountFromJson(
+
280 sfTakerPays,
+
281 aliceOffer[jss::node][sfTakerPays.jsonName]);
+
282 STAmount const aliceGot =
+
283 env.balance(alice) - aliceInitialBalance;
+
284 BEAST_EXPECT(reducedTakerPays < inLedger.in);
+
285 BEAST_EXPECT(reducedTakerGets < inLedger.out);
+
286 STAmount const inLedgerRate =
+
287 Quality(Amounts{reducedTakerPays, reducedTakerGets})
+
288 .rate();
+
289 badRate = inLedgerRate > initialRate ? 1 : 0;
+
290
+
291 // If the inLedgerRate is less than initial rate, then
+
292 // incrementing the mantissa of the reduced taker pays
+
293 // should result in a rate higher than initial. Check
+
294 // this to verify that the largest allowable TakerPays
+
295 // was computed.
+
296 if (badRate == 0)
+
297 {
+
298 STAmount const tweakedTakerPays =
+
299 reducedTakerPays + drops(1);
+
300 STAmount const tweakedRate =
+
301 Quality(Amounts{tweakedTakerPays, reducedTakerGets})
+
302 .rate();
+
303 BEAST_EXPECT(tweakedRate > initialRate);
+
304 }
+
305#if 0
+
306 std::cout << "Placed rate: " << initialRate
+
307 << "; in-ledger rate: " << inLedgerRate
+
308 << "; TakerPays: " << reducedTakerPays
+
309 << "; TakerGets: " << reducedTakerGets
+
310 << "; alice already got: " << aliceGot
+
311 << std::endl;
+
312// #else
+
313 std::string_view filler = badRate ? "**" : " ";
+
314 std::cout << "| `" << reducedTakerGets << "` | `"
+
315 << reducedTakerPays << "` | `" << initialRate
+
316 << "` | " << filler << "`" << inLedgerRate << "`"
+
317 << filler << " | `" << aliceGot << "` |"
+
318 << std::endl;
+
319#endif
+
320 }
+
321
+
322 // In preparation for the next iteration make sure the two
+
323 // offers are gone from the ledger.
+ +
325 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
+
326 return badRate;
+
327 };
+
328
+
329 // alice's offer (the old offer) is the same every time:
+
330 Amounts const aliceOffer{
+
331 STAmount(XRP(1)), STAmount(USD.issue(), 1, 0)};
+
332
+
333 // bob's offer has a slightly smaller TakerPays with each iteration.
+
334 // This should mean that the size of the offer alice leaves in the
+
335 // ledger should increase with each iteration.
+
336 unsigned int blockedCount = 0;
+
337 for (std::uint64_t mantissaReduce = 1'000'000'000ull;
+
338 mantissaReduce <= 4'000'000'000ull;
+
339 mantissaReduce += 20'000'000ull)
+
340 {
+
341 STAmount bobUSD{
+
342 aliceOffer.out.issue(),
+
343 aliceOffer.out.mantissa() - mantissaReduce,
+
344 aliceOffer.out.exponent()};
+
345 STAmount bobXRP{
+
346 aliceOffer.in.issue(), aliceOffer.in.mantissa() - 1};
+
347 Amounts bobsOffer{bobUSD, bobXRP};
+
348
+
349 blockedCount += exerciseOfferPair(aliceOffer, bobsOffer);
+
350 }
+
351
+
352 // None of the test cases should produce a potentially blocking
+
353 // rate.
+
354 BEAST_EXPECT(blockedCount == 0);
+
355 }
+
356 }
+
357
+
358 void
+
+ +
360 {
+
361 testcase("exercise underfunded XRP/IOU offer Q change");
+
362
+
363 // Bob places an offer that is not fully funded.
+
364
+
365 using namespace jtx;
+
366 auto const alice = Account{"alice"};
+
367 auto const bob = Account{"bob"};
+
368 auto const gw = Account{"gw"};
+
369 auto const USD = gw["USD"];
+
370
+
371 {
+
372 Env env{*this, testable_amendments()};
+
373
+
374 env.fund(XRP(10000), alice, bob, gw);
+
375 env.close();
+
376 env.trust(USD(1000), alice, bob);
+
377
+
378 int blockedOrderBookCount = 0;
+
379 for (STAmount initialBobUSD = USD(0.45); initialBobUSD <= USD(1);
+
380 initialBobUSD += USD(0.025))
+
381 {
+
382 // underfund bob's offer
+
383 env(pay(gw, bob, initialBobUSD));
+
384 env.close();
385
-
386 void
-
- -
388 {
-
389 testcase("exercise underfunded XRP/IOU offer Q change");
-
390
-
391 // Bob places an offer that is not fully funded.
-
392 //
-
393 // This unit test compares the behavior of this situation before and
-
394 // after applying the fixReducedOffersV1 amendment.
+
386 std::uint32_t const bobOfferSeq = env.seq(bob);
+
387 env(offer(bob, drops(2), USD(1)));
+
388 env.close();
+
389
+
390 // alice places an offer that would cross bob's if bob's were
+
391 // well funded.
+
392 std::uint32_t const aliceOfferSeq = env.seq(alice);
+
393 env(offer(alice, USD(1), drops(2)));
+
394 env.close();
395
-
396 using namespace jtx;
-
397 auto const alice = Account{"alice"};
-
398 auto const bob = Account{"bob"};
-
399 auto const gw = Account{"gw"};
-
400 auto const USD = gw["USD"];
-
401
-
402 // Make one test run without fixReducedOffersV1 and one with.
-
403 for (FeatureBitset features :
-
404 {testable_amendments() - fixReducedOffersV1,
-
405 testable_amendments() | fixReducedOffersV1})
-
406 {
-
407 Env env{*this, features};
-
408
-
409 env.fund(XRP(10000), alice, bob, gw);
-
410 env.close();
-
411 env.trust(USD(1000), alice, bob);
-
412
-
413 int blockedOrderBookCount = 0;
-
414 for (STAmount initialBobUSD = USD(0.45); initialBobUSD <= USD(1);
-
415 initialBobUSD += USD(0.025))
-
416 {
-
417 // underfund bob's offer
-
418 env(pay(gw, bob, initialBobUSD));
-
419 env.close();
-
420
-
421 std::uint32_t const bobOfferSeq = env.seq(bob);
-
422 env(offer(bob, drops(2), USD(1)));
-
423 env.close();
+
396 // We want to detect order book blocking. If:
+
397 // 1. bob's offer is still in the ledger and
+
398 // 2. alice received no USD
+
399 // then we use that as evidence that bob's offer blocked the
+
400 // order book.
+
401 {
+
402 bool const bobsOfferGone =
+
403 !offerInLedger(env, bob, bobOfferSeq);
+
404 STAmount const aliceBalanceUSD = env.balance(alice, USD);
+
405
+
406 // Sanity check the ledger if alice got USD.
+
407 if (aliceBalanceUSD.signum() > 0)
+
408 {
+
409 BEAST_EXPECT(aliceBalanceUSD == initialBobUSD);
+
410 BEAST_EXPECT(env.balance(bob, USD) == USD(0));
+
411 BEAST_EXPECT(bobsOfferGone);
+
412 }
+
413
+
414 // Track occurrences of order book blocking.
+
415 if (!bobsOfferGone && aliceBalanceUSD.signum() == 0)
+
416 {
+
417 ++blockedOrderBookCount;
+
418 }
+
419
+
420 // In preparation for the next iteration clean up any
+
421 // leftover offers.
+ +
423 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
424
-
425 // alice places an offer that would cross bob's if bob's were
-
426 // well funded.
-
427 std::uint32_t const aliceOfferSeq = env.seq(alice);
-
428 env(offer(alice, USD(1), drops(2)));
-
429 env.close();
-
430
-
431 // We want to detect order book blocking. If:
-
432 // 1. bob's offer is still in the ledger and
-
433 // 2. alice received no USD
-
434 // then we use that as evidence that bob's offer blocked the
-
435 // order book.
-
436 {
-
437 bool const bobsOfferGone =
-
438 !offerInLedger(env, bob, bobOfferSeq);
-
439 STAmount const aliceBalanceUSD = env.balance(alice, USD);
-
440
-
441 // Sanity check the ledger if alice got USD.
-
442 if (aliceBalanceUSD.signum() > 0)
-
443 {
-
444 BEAST_EXPECT(aliceBalanceUSD == initialBobUSD);
-
445 BEAST_EXPECT(env.balance(bob, USD) == USD(0));
-
446 BEAST_EXPECT(bobsOfferGone);
-
447 }
+
425 // Zero out alice's and bob's USD balances.
+
426 if (STAmount const aliceBalance = env.balance(alice, USD);
+
427 aliceBalance.signum() > 0)
+
428 env(pay(alice, gw, aliceBalance));
+
429
+
430 if (STAmount const bobBalance = env.balance(bob, USD);
+
431 bobBalance.signum() > 0)
+
432 env(pay(bob, gw, bobBalance));
+
433
+
434 env.close();
+
435 }
+
436 }
+
437
+
438 // None of the test cases should produce a potentially blocking
+
439 // rate.
+
440 BEAST_EXPECT(blockedOrderBookCount == 0);
+
441 }
+
442 }
+
+
443
+
444 void
+
+ +
446 {
+
447 testcase("exercise underfunded IOU/IOU offer Q change");
448
-
449 // Track occurrences of order book blocking.
-
450 if (!bobsOfferGone && aliceBalanceUSD.signum() == 0)
-
451 {
-
452 ++blockedOrderBookCount;
-
453 }
-
454
-
455 // In preparation for the next iteration clean up any
-
456 // leftover offers.
- -
458 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
+
449 // Bob places an IOU/IOU offer that is not fully funded.
+
450
+
451 using namespace jtx;
+
452 using namespace std::chrono_literals;
+
453 auto const alice = Account{"alice"};
+
454 auto const bob = Account{"bob"};
+
455 auto const gw = Account{"gw"};
+
456
+
457 auto const USD = gw["USD"];
+
458 auto const EUR = gw["EUR"];
459
-
460 // Zero out alice's and bob's USD balances.
-
461 if (STAmount const aliceBalance = env.balance(alice, USD);
-
462 aliceBalance.signum() > 0)
-
463 env(pay(alice, gw, aliceBalance));
+
460 STAmount const tinyUSD(USD.issue(), /*mantissa*/ 1, /*exponent*/ -81);
+
461
+
462 {
+
463 Env env{*this, testable_amendments()};
464
-
465 if (STAmount const bobBalance = env.balance(bob, USD);
-
466 bobBalance.signum() > 0)
-
467 env(pay(bob, gw, bobBalance));
-
468
-
469 env.close();
-
470 }
-
471 }
-
472
-
473 // If fixReducedOffersV1 is enabled, then none of the test cases
-
474 // should produce a potentially blocking rate.
-
475 //
-
476 // Also verify that if fixReducedOffersV1 is not enabled then
-
477 // some of the test cases produced a potentially blocking rate.
-
478 if (features[fixReducedOffersV1])
-
479 {
-
480 BEAST_EXPECT(blockedOrderBookCount == 0);
-
481 }
-
482 else
-
483 {
-
484 BEAST_EXPECT(blockedOrderBookCount > 15);
-
485 }
-
486 }
-
487 }
-
-
488
-
489 void
-
- -
491 {
-
492 testcase("exercise underfunded IOU/IOU offer Q change");
-
493
-
494 // Bob places an IOU/IOU offer that is not fully funded.
-
495 //
-
496 // This unit test compares the behavior of this situation before and
-
497 // after applying the fixReducedOffersV1 amendment.
-
498
-
499 using namespace jtx;
-
500 using namespace std::chrono_literals;
-
501 auto const alice = Account{"alice"};
-
502 auto const bob = Account{"bob"};
-
503 auto const gw = Account{"gw"};
-
504
-
505 auto const USD = gw["USD"];
-
506 auto const EUR = gw["EUR"];
-
507
-
508 STAmount const tinyUSD(USD.issue(), /*mantissa*/ 1, /*exponent*/ -81);
-
509
-
510 // Make one test run without fixReducedOffersV1 and one with.
-
511 for (FeatureBitset features :
-
512 {testable_amendments() - fixReducedOffersV1,
-
513 testable_amendments() | fixReducedOffersV1})
-
514 {
-
515 Env env{*this, features};
-
516
-
517 env.fund(XRP(10000), alice, bob, gw);
-
518 env.close();
-
519 env.trust(USD(1000), alice, bob);
-
520 env.trust(EUR(1000), alice, bob);
-
521
-
522 STAmount const eurOffer(
-
523 EUR.issue(), /*mantissa*/ 2957, /*exponent*/ -76);
-
524 STAmount const usdOffer(
-
525 USD.issue(), /*mantissa*/ 7109, /*exponent*/ -76);
-
526
-
527 STAmount const endLoop(
-
528 USD.issue(), /*mantissa*/ 50, /*exponent*/ -81);
+
465 env.fund(XRP(10000), alice, bob, gw);
+
466 env.close();
+
467 env.trust(USD(1000), alice, bob);
+
468 env.trust(EUR(1000), alice, bob);
+
469
+
470 STAmount const eurOffer(
+
471 EUR.issue(), /*mantissa*/ 2957, /*exponent*/ -76);
+
472 STAmount const usdOffer(
+
473 USD.issue(), /*mantissa*/ 7109, /*exponent*/ -76);
+
474
+
475 STAmount const endLoop(
+
476 USD.issue(), /*mantissa*/ 50, /*exponent*/ -81);
+
477
+
478 int blockedOrderBookCount = 0;
+
479 for (STAmount initialBobUSD = tinyUSD; initialBobUSD <= endLoop;
+
480 initialBobUSD += tinyUSD)
+
481 {
+
482 // underfund bob's offer
+
483 env(pay(gw, bob, initialBobUSD));
+
484 env(pay(gw, alice, EUR(100)));
+
485 env.close();
+
486
+
487 // This offer is underfunded
+
488 std::uint32_t bobOfferSeq = env.seq(bob);
+
489 env(offer(bob, eurOffer, usdOffer));
+
490 env.close();
+
491 env.require(offers(bob, 1));
+
492
+
493 // alice places an offer that crosses bob's.
+
494 std::uint32_t aliceOfferSeq = env.seq(alice);
+
495 env(offer(alice, usdOffer, eurOffer));
+
496 env.close();
+
497
+
498 // Examine the aftermath of alice's offer.
+
499 {
+
500 bool const bobsOfferGone =
+
501 !offerInLedger(env, bob, bobOfferSeq);
+
502 STAmount aliceBalanceUSD = env.balance(alice, USD);
+
503#if 0
+ +
505 << "bobs initial: " << initialBobUSD
+
506 << "; alice final: " << aliceBalanceUSD
+
507 << "; bobs offer: " << bobsOfferJson.toStyledString()
+
508 << std::endl;
+
509#endif
+
510 // Sanity check the ledger if alice got USD.
+
511 if (aliceBalanceUSD.signum() > 0)
+
512 {
+
513 BEAST_EXPECT(aliceBalanceUSD == initialBobUSD);
+
514 BEAST_EXPECT(env.balance(bob, USD) == USD(0));
+
515 BEAST_EXPECT(bobsOfferGone);
+
516 }
+
517
+
518 // Track occurrences of order book blocking.
+
519 if (!bobsOfferGone && aliceBalanceUSD.signum() == 0)
+
520 {
+
521 ++blockedOrderBookCount;
+
522 }
+
523 }
+
524
+
525 // In preparation for the next iteration clean up any
+
526 // leftover offers.
+ +
528 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
529
-
530 int blockedOrderBookCount = 0;
-
531 for (STAmount initialBobUSD = tinyUSD; initialBobUSD <= endLoop;
-
532 initialBobUSD += tinyUSD)
-
533 {
-
534 // underfund bob's offer
-
535 env(pay(gw, bob, initialBobUSD));
-
536 env(pay(gw, alice, EUR(100)));
-
537 env.close();
-
538
-
539 // This offer is underfunded
-
540 std::uint32_t bobOfferSeq = env.seq(bob);
-
541 env(offer(bob, eurOffer, usdOffer));
+
530 // Zero out alice's and bob's IOU balances.
+
531 auto zeroBalance = [&env, &gw](
+
532 Account const& acct, IOU const& iou) {
+
533 if (STAmount const balance = env.balance(acct, iou);
+
534 balance.signum() > 0)
+
535 env(pay(acct, gw, balance));
+
536 };
+
537
+
538 zeroBalance(alice, EUR);
+
539 zeroBalance(alice, USD);
+
540 zeroBalance(bob, EUR);
+
541 zeroBalance(bob, USD);
542 env.close();
-
543 env.require(offers(bob, 1));
+
543 }
544
-
545 // alice places an offer that crosses bob's.
-
546 std::uint32_t aliceOfferSeq = env.seq(alice);
-
547 env(offer(alice, usdOffer, eurOffer));
-
548 env.close();
-
549
-
550 // Examine the aftermath of alice's offer.
-
551 {
-
552 bool const bobsOfferGone =
-
553 !offerInLedger(env, bob, bobOfferSeq);
-
554 STAmount aliceBalanceUSD = env.balance(alice, USD);
-
555#if 0
- -
557 << "bobs initial: " << initialBobUSD
-
558 << "; alice final: " << aliceBalanceUSD
-
559 << "; bobs offer: " << bobsOfferJson.toStyledString()
-
560 << std::endl;
-
561#endif
-
562 // Sanity check the ledger if alice got USD.
-
563 if (aliceBalanceUSD.signum() > 0)
-
564 {
-
565 BEAST_EXPECT(aliceBalanceUSD == initialBobUSD);
-
566 BEAST_EXPECT(env.balance(bob, USD) == USD(0));
-
567 BEAST_EXPECT(bobsOfferGone);
-
568 }
+
545 // None of the test cases should produce a potentially blocking
+
546 // rate.
+
547 BEAST_EXPECT(blockedOrderBookCount == 0);
+
548 }
+
549 }
+
+
550
+
551 Amounts
+
+ +
553 {
+
554 STAmount const in =
+
555 amountFromJson(sfTakerPays, json[sfTakerPays.jsonName]);
+
556 STAmount const out =
+
557 amountFromJson(sfTakerGets, json[sfTakerGets.jsonName]);
+
558 return {in, out};
+
559 }
+
+
560
+
561 void
+
+ +
563 {
+
564 // This test case was motivated by Issue #4937. It recreates
+
565 // the specific failure identified in that issue and samples some other
+
566 // cases in the same vicinity to make sure that the new behavior makes
+
567 // sense.
+
568 testcase("exercise tfSell partial cross old XRP/IOU offer Q change");
569
-
570 // Track occurrences of order book blocking.
-
571 if (!bobsOfferGone && aliceBalanceUSD.signum() == 0)
-
572 {
-
573 ++blockedOrderBookCount;
-
574 }
-
575 }
-
576
-
577 // In preparation for the next iteration clean up any
-
578 // leftover offers.
- -
580 env, {{alice, aliceOfferSeq}, {bob, bobOfferSeq}});
-
581
-
582 // Zero out alice's and bob's IOU balances.
-
583 auto zeroBalance = [&env, &gw](
-
584 Account const& acct, IOU const& iou) {
-
585 if (STAmount const balance = env.balance(acct, iou);
-
586 balance.signum() > 0)
-
587 env(pay(acct, gw, balance));
-
588 };
-
589
-
590 zeroBalance(alice, EUR);
-
591 zeroBalance(alice, USD);
-
592 zeroBalance(bob, EUR);
-
593 zeroBalance(bob, USD);
-
594 env.close();
-
595 }
-
596
-
597 // If fixReducedOffersV1 is enabled, then none of the test cases
-
598 // should produce a potentially blocking rate.
-
599 //
-
600 // Also verify that if fixReducedOffersV1 is not enabled then
-
601 // some of the test cases produced a potentially blocking rate.
-
602 if (features[fixReducedOffersV1])
-
603 {
-
604 BEAST_EXPECT(blockedOrderBookCount == 0);
-
605 }
-
606 else
-
607 {
-
608 BEAST_EXPECT(blockedOrderBookCount > 20);
-
609 }
-
610 }
-
611 }
-
-
612
-
613 Amounts
-
- -
615 {
-
616 STAmount const in =
-
617 amountFromJson(sfTakerPays, json[sfTakerPays.jsonName]);
-
618 STAmount const out =
-
619 amountFromJson(sfTakerGets, json[sfTakerGets.jsonName]);
-
620 return {in, out};
-
621 }
-
-
622
-
623 void
-
- -
625 {
-
626 // This test case was motivated by Issue #4937. It recreates
-
627 // the specific failure identified in that issue and samples some other
-
628 // cases in the same vicinity to make sure that the new behavior makes
-
629 // sense.
-
630 testcase("exercise tfSell partial cross old XRP/IOU offer Q change");
-
631
-
632 using namespace jtx;
-
633
-
634 Account const gw("gateway");
-
635 Account const alice("alice");
-
636 Account const bob("bob");
-
637 Account const carol("carol");
-
638 auto const USD = gw["USD"];
-
639
-
640 // Make one test run without fixReducedOffersV2 and one with.
-
641 for (FeatureBitset features :
-
642 {testable_amendments() - fixReducedOffersV2,
-
643 testable_amendments() | fixReducedOffersV2})
-
644 {
-
645 // Make sure none of the offers we generate are under funded.
-
646 Env env{*this, features};
-
647 env.fund(XRP(10'000'000), gw, alice, bob, carol);
-
648 env.close();
-
649
-
650 env(trust(alice, USD(10'000'000)));
-
651 env(trust(bob, USD(10'000'000)));
-
652 env(trust(carol, USD(10'000'000)));
-
653 env.close();
-
654
-
655 env(pay(gw, alice, USD(10'000'000)));
-
656 env(pay(gw, bob, USD(10'000'000)));
-
657 env(pay(gw, carol, USD(10'000'000)));
-
658 env.close();
-
659
-
660 // Lambda that:
-
661 // 1. Exercises one offer trio,
-
662 // 2. Collects the results, and
-
663 // 3. Cleans up for the next offer trio.
-
664 auto exerciseOfferTrio =
-
665 [this, &env, &alice, &bob, &carol, &USD](
-
666 Amounts const& carolOffer) -> unsigned int {
-
667 // alice submits an offer that may become a blocker.
-
668 std::uint32_t const aliceOfferSeq = env.seq(alice);
-
669 static Amounts const aliceInitialOffer(USD(2), drops(3382562));
-
670 env(offer(alice, aliceInitialOffer.in, aliceInitialOffer.out));
-
671 env.close();
-
672 STAmount const initialRate =
- -
674 env, alice, aliceOfferSeq)[jss::node]))
-
675 .rate();
-
676
-
677 // bob submits an offer that is more desirable than alice's
-
678 std::uint32_t const bobOfferSeq = env.seq(bob);
-
679 env(offer(bob, USD(0.97086565812384), drops(1642020)));
-
680 env.close();
-
681
-
682 // Now carol's offer consumes bob's and partially crosses
-
683 // alice's. The tfSell flag is important.
-
684 std::uint32_t const carolOfferSeq = env.seq(carol);
-
685 env(offer(carol, carolOffer.in, carolOffer.out),
-
686 txflags(tfSell));
-
687 env.close();
-
688
-
689 // carol's offer should not have made it into the ledger and
-
690 // bob's offer should be fully consumed.
-
691 if (!BEAST_EXPECT(
-
692 !offerInLedger(env, carol, carolOfferSeq) &&
-
693 !offerInLedger(env, bob, bobOfferSeq)))
-
694 {
-
695 // If carol's or bob's offers are still in the ledger then
-
696 // further results are meaningless.
- -
698 env,
-
699 {{alice, aliceOfferSeq},
-
700 {bob, bobOfferSeq},
-
701 {carol, carolOfferSeq}});
-
702 return 1;
-
703 }
-
704 // alice's offer should still be in the ledger, but reduced in
-
705 // size.
-
706 unsigned int badRate = 1;
+
570 using namespace jtx;
+
571
+
572 Account const gw("gateway");
+
573 Account const alice("alice");
+
574 Account const bob("bob");
+
575 Account const carol("carol");
+
576 auto const USD = gw["USD"];
+
577
+
578 // Make one test run without fixReducedOffersV2 and one with.
+
579 for (FeatureBitset features :
+
580 {testable_amendments() - fixReducedOffersV2,
+
581 testable_amendments() | fixReducedOffersV2})
+
582 {
+
583 // Make sure none of the offers we generate are under funded.
+
584 Env env{*this, features};
+
585 env.fund(XRP(10'000'000), gw, alice, bob, carol);
+
586 env.close();
+
587
+
588 env(trust(alice, USD(10'000'000)));
+
589 env(trust(bob, USD(10'000'000)));
+
590 env(trust(carol, USD(10'000'000)));
+
591 env.close();
+
592
+
593 env(pay(gw, alice, USD(10'000'000)));
+
594 env(pay(gw, bob, USD(10'000'000)));
+
595 env(pay(gw, carol, USD(10'000'000)));
+
596 env.close();
+
597
+
598 // Lambda that:
+
599 // 1. Exercises one offer trio,
+
600 // 2. Collects the results, and
+
601 // 3. Cleans up for the next offer trio.
+
602 auto exerciseOfferTrio =
+
603 [this, &env, &alice, &bob, &carol, &USD](
+
604 Amounts const& carolOffer) -> unsigned int {
+
605 // alice submits an offer that may become a blocker.
+
606 std::uint32_t const aliceOfferSeq = env.seq(alice);
+
607 static Amounts const aliceInitialOffer(USD(2), drops(3382562));
+
608 env(offer(alice, aliceInitialOffer.in, aliceInitialOffer.out));
+
609 env.close();
+
610 STAmount const initialRate =
+ +
612 env, alice, aliceOfferSeq)[jss::node]))
+
613 .rate();
+
614
+
615 // bob submits an offer that is more desirable than alice's
+
616 std::uint32_t const bobOfferSeq = env.seq(bob);
+
617 env(offer(bob, USD(0.97086565812384), drops(1642020)));
+
618 env.close();
+
619
+
620 // Now carol's offer consumes bob's and partially crosses
+
621 // alice's. The tfSell flag is important.
+
622 std::uint32_t const carolOfferSeq = env.seq(carol);
+
623 env(offer(carol, carolOffer.in, carolOffer.out),
+
624 txflags(tfSell));
+
625 env.close();
+
626
+
627 // carol's offer should not have made it into the ledger and
+
628 // bob's offer should be fully consumed.
+
629 if (!BEAST_EXPECT(
+
630 !offerInLedger(env, carol, carolOfferSeq) &&
+
631 !offerInLedger(env, bob, bobOfferSeq)))
+
632 {
+
633 // If carol's or bob's offers are still in the ledger then
+
634 // further results are meaningless.
+ +
636 env,
+
637 {{alice, aliceOfferSeq},
+
638 {bob, bobOfferSeq},
+
639 {carol, carolOfferSeq}});
+
640 return 1;
+
641 }
+
642 // alice's offer should still be in the ledger, but reduced in
+
643 // size.
+
644 unsigned int badRate = 1;
+
645 {
+
646 Json::Value aliceOffer =
+
647 ledgerEntryOffer(env, alice, aliceOfferSeq);
+
648
+
649 Amounts aliceReducedOffer =
+
650 jsonOfferToAmounts(aliceOffer[jss::node]);
+
651
+
652 BEAST_EXPECT(aliceReducedOffer.in < aliceInitialOffer.in);
+
653 BEAST_EXPECT(aliceReducedOffer.out < aliceInitialOffer.out);
+
654 STAmount const inLedgerRate =
+
655 Quality(aliceReducedOffer).rate();
+
656 badRate = inLedgerRate > initialRate ? 1 : 0;
+
657
+
658 // If the inLedgerRate is less than initial rate, then
+
659 // incrementing the mantissa of the reduced TakerGets
+
660 // should result in a rate higher than initial. Check
+
661 // this to verify that the largest allowable TakerGets
+
662 // was computed.
+
663 if (badRate == 0)
+
664 {
+
665 STAmount const tweakedTakerGets(
+
666 aliceReducedOffer.in.issue(),
+
667 aliceReducedOffer.in.mantissa() + 1,
+
668 aliceReducedOffer.in.exponent(),
+
669 aliceReducedOffer.in.negative());
+
670 STAmount const tweakedRate =
+
671 Quality(
+
672 Amounts{aliceReducedOffer.in, tweakedTakerGets})
+
673 .rate();
+
674 BEAST_EXPECT(tweakedRate > initialRate);
+
675 }
+
676#if 0
+
677 std::cout << "Placed rate: " << initialRate
+
678 << "; in-ledger rate: " << inLedgerRate
+
679 << "; TakerPays: " << aliceReducedOffer.in
+
680 << "; TakerGets: " << aliceReducedOffer.out
+
681 << std::endl;
+
682// #else
+
683 std::string_view filler = badRate ? "**" : " ";
+
684 std::cout << "| " << aliceReducedOffer.in << "` | `"
+
685 << aliceReducedOffer.out << "` | `" << initialRate
+
686 << "` | " << filler << "`" << inLedgerRate << "`"
+
687 << filler << std::endl;
+
688#endif
+
689 }
+
690
+
691 // In preparation for the next iteration make sure all three
+
692 // offers are gone from the ledger.
+ +
694 env,
+
695 {{alice, aliceOfferSeq},
+
696 {bob, bobOfferSeq},
+
697 {carol, carolOfferSeq}});
+
698 return badRate;
+
699 };
+
700
+
701 constexpr int loopCount = 100;
+
702 unsigned int blockedCount = 0;
+
703 {
+
704 STAmount increaseGets = USD(0);
+
705 STAmount const step(increaseGets.issue(), 1, -8);
+
706 for (unsigned int i = 0; i < loopCount; ++i)
707 {
-
708 Json::Value aliceOffer =
-
709 ledgerEntryOffer(env, alice, aliceOfferSeq);
-
710
-
711 Amounts aliceReducedOffer =
-
712 jsonOfferToAmounts(aliceOffer[jss::node]);
+
708 blockedCount += exerciseOfferTrio(
+
709 Amounts(drops(1642020), USD(1) + increaseGets));
+
710 increaseGets += step;
+
711 }
+
712 }
713
-
714 BEAST_EXPECT(aliceReducedOffer.in < aliceInitialOffer.in);
-
715 BEAST_EXPECT(aliceReducedOffer.out < aliceInitialOffer.out);
-
716 STAmount const inLedgerRate =
-
717 Quality(aliceReducedOffer).rate();
-
718 badRate = inLedgerRate > initialRate ? 1 : 0;
-
719
-
720 // If the inLedgerRate is less than initial rate, then
-
721 // incrementing the mantissa of the reduced TakerGets
-
722 // should result in a rate higher than initial. Check
-
723 // this to verify that the largest allowable TakerGets
-
724 // was computed.
-
725 if (badRate == 0)
-
726 {
-
727 STAmount const tweakedTakerGets(
-
728 aliceReducedOffer.in.issue(),
-
729 aliceReducedOffer.in.mantissa() + 1,
-
730 aliceReducedOffer.in.exponent(),
-
731 aliceReducedOffer.in.negative());
-
732 STAmount const tweakedRate =
-
733 Quality(
-
734 Amounts{aliceReducedOffer.in, tweakedTakerGets})
-
735 .rate();
-
736 BEAST_EXPECT(tweakedRate > initialRate);
-
737 }
-
738#if 0
-
739 std::cout << "Placed rate: " << initialRate
-
740 << "; in-ledger rate: " << inLedgerRate
-
741 << "; TakerPays: " << aliceReducedOffer.in
-
742 << "; TakerGets: " << aliceReducedOffer.out
-
743 << std::endl;
-
744// #else
-
745 std::string_view filler = badRate ? "**" : " ";
-
746 std::cout << "| " << aliceReducedOffer.in << "` | `"
-
747 << aliceReducedOffer.out << "` | `" << initialRate
-
748 << "` | " << filler << "`" << inLedgerRate << "`"
-
749 << filler << std::endl;
-
750#endif
-
751 }
-
752
-
753 // In preparation for the next iteration make sure all three
-
754 // offers are gone from the ledger.
- -
756 env,
-
757 {{alice, aliceOfferSeq},
-
758 {bob, bobOfferSeq},
-
759 {carol, carolOfferSeq}});
-
760 return badRate;
-
761 };
-
762
-
763 constexpr int loopCount = 100;
-
764 unsigned int blockedCount = 0;
-
765 {
-
766 STAmount increaseGets = USD(0);
-
767 STAmount const step(increaseGets.issue(), 1, -8);
-
768 for (unsigned int i = 0; i < loopCount; ++i)
-
769 {
-
770 blockedCount += exerciseOfferTrio(
-
771 Amounts(drops(1642020), USD(1) + increaseGets));
-
772 increaseGets += step;
-
773 }
-
774 }
-
775
-
776 // If fixReducedOffersV2 is enabled, then none of the test cases
-
777 // should produce a potentially blocking rate.
-
778 //
-
779 // Also verify that if fixReducedOffersV2 is not enabled then
-
780 // some of the test cases produced a potentially blocking rate.
-
781 if (features[fixReducedOffersV2])
-
782 {
-
783 BEAST_EXPECT(blockedCount == 0);
-
784 }
-
785 else
-
786 {
-
787 BEAST_EXPECT(blockedCount > 80);
-
788 }
-
789 }
-
790 }
+
714 // If fixReducedOffersV2 is enabled, then none of the test cases
+
715 // should produce a potentially blocking rate.
+
716 //
+
717 // Also verify that if fixReducedOffersV2 is not enabled then
+
718 // some of the test cases produced a potentially blocking rate.
+
719 if (features[fixReducedOffersV2])
+
720 {
+
721 BEAST_EXPECT(blockedCount == 0);
+
722 }
+
723 else
+
724 {
+
725 BEAST_EXPECT(blockedCount > 80);
+
726 }
+
727 }
+
728 }
-
791
-
792 void
- -
802
-
803BEAST_DEFINE_TESTSUITE_PRIO(ReducedOffer, app, ripple, 2);
-
804
-
805} // namespace test
-
806} // namespace ripple
+
740
+
741BEAST_DEFINE_TESTSUITE_PRIO(ReducedOffer, app, ripple, 2);
+
742
+
743} // namespace test
+
744} // namespace ripple
Represents a JSON value.
Definition json_value.h:149
std::string asString() const
Returns the unquoted string value.
@@ -920,15 +858,15 @@ $(document).ready(function() { init_codefold(0); });
int signum() const noexcept
Definition STAmount.h:514
Issue const & issue() const
Definition STAmount.h:496
- - -
void run() override
Runs the suite.
- - + + +
void run() override
Runs the suite.
+ +
static void cleanupOldOffers(jtx::Env &env, std::initializer_list< std::pair< jtx::Account const &, std::uint32_t > > list)
static bool offerInLedger(jtx::Env &env, jtx::Account const &acct, std::uint32_t offerSeq)
static auto ledgerEntryOffer(jtx::Env &env, jtx::Account const &acct, std::uint32_t offer_seq)
-
Amounts jsonOfferToAmounts(Json::Value const &json)
+
Amounts jsonOfferToAmounts(Json::Value const &json)
Immutable cryptographic account descriptor.
Definition Account.h:39
std::string const & human() const
Returns the human readable public key.
Definition Account.h:118
diff --git a/classripple_1_1AMMOffer.html b/classripple_1_1AMMOffer.html index c30a7bada8..9ee614fe59 100644 --- a/classripple_1_1AMMOffer.html +++ b/classripple_1_1AMMOffer.html @@ -437,7 +437,7 @@ template<typename TIn , typename TOut >

Limit in of the provided offer.

If one-path then swapIn using current balances. If multi-path then ceil_in using current quality.

-

Definition at line 112 of file AMMOffer.cpp.

+

Definition at line 107 of file AMMOffer.cpp.

@@ -458,7 +458,7 @@ template<typename TIn , typename TOut >
-

Definition at line 131 of file AMMOffer.cpp.

+

Definition at line 126 of file AMMOffer.cpp.

@@ -587,7 +587,7 @@ template<typename TIn , typename TOut >

Check the new pool product is greater or equal to the old pool product or if decreases then within some threshold.

-

Definition at line 141 of file AMMOffer.cpp.

+

Definition at line 136 of file AMMOffer.cpp.

diff --git a/classripple_1_1CreateOffer.html b/classripple_1_1CreateOffer.html index ec3ed0ed2e..44d7ea5f59 100644 --- a/classripple_1_1CreateOffer.html +++ b/classripple_1_1CreateOffer.html @@ -612,7 +612,7 @@ Static Private Member Functions

Implements ripple::Transactor.

-

Definition at line 940 of file CreateOffer.cpp.

+

Definition at line 926 of file CreateOffer.cpp.

@@ -650,7 +650,7 @@ Static Private Member Functions
-

Definition at line 582 of file CreateOffer.cpp.

+

Definition at line 568 of file CreateOffer.cpp.

@@ -784,7 +784,7 @@ Static Private Member Functions
-

Definition at line 525 of file CreateOffer.cpp.

+

Definition at line 511 of file CreateOffer.cpp.

@@ -846,7 +846,7 @@ Static Private Member Functions
-

Definition at line 534 of file CreateOffer.cpp.

+

Definition at line 520 of file CreateOffer.cpp.

diff --git a/classripple_1_1FlowOfferStream.html b/classripple_1_1FlowOfferStream.html index c98d7aa88b..a53c12f0dc 100644 --- a/classripple_1_1FlowOfferStream.html +++ b/classripple_1_1FlowOfferStream.html @@ -307,7 +307,7 @@ template<class TIn , class TOut >

Implements ripple::TOfferStreamBase< TIn, TOut >.

-

Definition at line 419 of file OfferStream.cpp.

+

Definition at line 411 of file OfferStream.cpp.

@@ -458,7 +458,7 @@ template<class TIn , class TOut > -

Definition at line 223 of file OfferStream.cpp.

+

Definition at line 215 of file OfferStream.cpp.

diff --git a/classripple_1_1OfferStream.html b/classripple_1_1OfferStream.html index 3c401f5f57..40d4349e6c 100644 --- a/classripple_1_1OfferStream.html +++ b/classripple_1_1OfferStream.html @@ -296,7 +296,7 @@ Protected Attributes

Implements ripple::TOfferStreamBase< STAmount, STAmount >.

-

Definition at line 412 of file OfferStream.cpp.

+

Definition at line 404 of file OfferStream.cpp.

diff --git a/classripple_1_1TOffer.html b/classripple_1_1TOffer.html index f3fdc7faa1..4aa3c77e91 100644 --- a/classripple_1_1TOffer.html +++ b/classripple_1_1TOffer.html @@ -569,7 +569,7 @@ template<class TIn , class TOut >
-

Definition at line 313 of file Offer.h.

+

Definition at line 308 of file Offer.h.

@@ -590,7 +590,7 @@ template<class TIn , class TOut >
-

Definition at line 327 of file Offer.h.

+

Definition at line 322 of file Offer.h.

@@ -666,7 +666,7 @@ template<class TIn , class TOut >
-

Definition at line 256 of file Offer.h.

+

Definition at line 251 of file Offer.h.

@@ -698,7 +698,7 @@ template<typename... Args>
-

Definition at line 274 of file Offer.h.

+

Definition at line 269 of file Offer.h.

@@ -821,7 +821,7 @@ template<class TIn = STAmount, class TOut = STAmount>
-

Definition at line 281 of file Offer.h.

+

Definition at line 276 of file Offer.h.

@@ -848,7 +848,7 @@ template<class TIn = STAmount, class TOut = STAmount>
-

Definition at line 289 of file Offer.h.

+

Definition at line 284 of file Offer.h.

@@ -875,7 +875,7 @@ template<class TIn = STAmount, class TOut = STAmount>
-

Definition at line 297 of file Offer.h.

+

Definition at line 292 of file Offer.h.

@@ -902,7 +902,7 @@ template<class TIn = STAmount, class TOut = STAmount>
-

Definition at line 305 of file Offer.h.

+

Definition at line 300 of file Offer.h.

@@ -921,7 +921,7 @@ template<class TIn = STAmount, class TOut = STAmount>
-

Definition at line 320 of file Offer.h.

+

Definition at line 315 of file Offer.h.

@@ -940,7 +940,7 @@ template<class TIn = STAmount, class TOut = STAmount>
-

Definition at line 334 of file Offer.h.

+

Definition at line 329 of file Offer.h.

diff --git a/classripple_1_1TOfferStreamBase.html b/classripple_1_1TOfferStreamBase.html index 4e79f6546e..41c4ef22b4 100644 --- a/classripple_1_1TOfferStreamBase.html +++ b/classripple_1_1TOfferStreamBase.html @@ -493,7 +493,7 @@ template<class TIn , class TOut > -

Definition at line 223 of file OfferStream.cpp.

+

Definition at line 215 of file OfferStream.cpp.

diff --git a/classripple_1_1test_1_1ReducedOffer__test.html b/classripple_1_1test_1_1ReducedOffer__test.html index 7c97fd75c5..e61e59011b 100644 --- a/classripple_1_1test_1_1ReducedOffer__test.html +++ b/classripple_1_1test_1_1ReducedOffer__test.html @@ -420,7 +420,7 @@ Private Attributes
-

Definition at line 228 of file ReducedOffer_test.cpp.

+

Definition at line 214 of file ReducedOffer_test.cpp.

@@ -439,7 +439,7 @@ Private Attributes
-

Definition at line 387 of file ReducedOffer_test.cpp.

+

Definition at line 359 of file ReducedOffer_test.cpp.

@@ -458,7 +458,7 @@ Private Attributes
-

Definition at line 490 of file ReducedOffer_test.cpp.

+

Definition at line 445 of file ReducedOffer_test.cpp.

@@ -478,7 +478,7 @@ Private Attributes
-

Definition at line 614 of file ReducedOffer_test.cpp.

+

Definition at line 552 of file ReducedOffer_test.cpp.

@@ -497,7 +497,7 @@ Private Attributes
-

Definition at line 624 of file ReducedOffer_test.cpp.

+

Definition at line 562 of file ReducedOffer_test.cpp.

@@ -528,7 +528,7 @@ Private Attributes

Implements beast::unit_test::suite.

-

Definition at line 793 of file ReducedOffer_test.cpp.

+

Definition at line 731 of file ReducedOffer_test.cpp.

diff --git a/namespaceripple.html b/namespaceripple.html index a13a6207bb..950966193c 100644 --- a/namespaceripple.html +++ b/namespaceripple.html @@ -42738,7 +42738,7 @@ template<class TIn , class TOut >
-

Definition at line 340 of file Offer.h.

+

Definition at line 335 of file Offer.h.