20 #include <ripple/app/ledger/OrderBookDB.h>
21 #include <ripple/app/paths/Flow.h>
22 #include <ripple/app/tx/impl/CreateOffer.h>
23 #include <ripple/beast/utility/WrappedSink.h>
24 #include <ripple/ledger/CashDiff.h>
25 #include <ripple/ledger/PaymentSandbox.h>
26 #include <ripple/protocol/Feature.h>
27 #include <ripple/protocol/Quality.h>
28 #include <ripple/protocol/st.h>
37 return saTakerGets.native() ? saTakerGets.xrp() : beast::zero;
54 JLOG(j.debug()) <<
"Malformed transaction: Invalid flags set.";
61 if (bImmediateOrCancel && bFillOrKill)
63 JLOG(j.debug()) <<
"Malformed transaction: both IoC and FoK set.";
67 bool const bHaveExpiration(tx.isFieldPresent(
sfExpiration));
69 if (bHaveExpiration && (tx.getFieldU32(
sfExpiration) == 0))
71 JLOG(j.debug()) <<
"Malformed offer: bad expiration";
79 JLOG(j.debug()) <<
"Malformed offer: bad cancel sequence";
91 JLOG(j.debug()) <<
"Malformed offer: redundant (XRP for XRP)";
94 if (saTakerPays <= beast::zero || saTakerGets <= beast::zero)
96 JLOG(j.debug()) <<
"Malformed offer: bad amount";
100 auto const& uPaysIssuerID = saTakerPays.
getIssuer();
101 auto const& uPaysCurrency = saTakerPays.
getCurrency();
103 auto const& uGetsIssuerID = saTakerGets.
getIssuer();
104 auto const& uGetsCurrency = saTakerGets.
getCurrency();
106 if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID)
108 JLOG(j.debug()) <<
"Malformed offer: redundant (IOU for IOU)";
114 JLOG(j.debug()) <<
"Malformed offer: bad currency";
118 if (saTakerPays.
native() != !uPaysIssuerID ||
119 saTakerGets.
native() != !uGetsIssuerID)
121 JLOG(j.warn()) <<
"Malformed offer: bad issuer";
136 auto const& uPaysIssuerID = saTakerPays.getIssuer();
137 auto const& uPaysCurrency = saTakerPays.getCurrency();
139 auto const& uGetsIssuerID = saTakerGets.getIssuer();
154 JLOG(ctx.
j.
info()) <<
"Offer involves frozen asset";
163 <<
"delay: Offers must be at least partially funded.";
169 else if (cancelSequence && (uAccountSequence <= *cancelSequence))
171 JLOG(ctx.
j.
debug()) <<
"uAccountSequenceNext=" << uAccountSequence
172 <<
" uOfferSequence=" << *cancelSequence;
197 if (!saTakerPays.native())
199 auto result = checkAcceptAsset(
204 Issue(uPaysCurrency, uPaysIssuerID));
213 CreateOffer::checkAcceptAsset(
223 auto const issuerAccount = view.
read(keylet::account(issue.
account));
227 JLOG(j.
warn()) <<
"delay: can't receive IOUs from non-existent issuer: "
242 auto const trustLine =
253 bool const canonical_gt(
id > issue.
account);
255 bool const is_authorized(
261 <<
"delay: can't receive IOUs from issuer without auth.";
273 if (offer.fully_consumed())
280 ctx_.app.journal(
"View"));
281 return (amount <= beast::zero);
285 CreateOffer::select_path(
293 assert(have_direct || have_bridge);
299 Quality
const bridged_quality(
306 Quality
const direct_quality(direct.
tip().
quality());
308 if (bridged_quality < direct_quality)
318 CreateOffer::reachedOfferCrossingLimit(
Taker const& taker)
const
320 auto const crossings =
325 return crossings >= 850;
329 CreateOffer::bridged_cross(
337 assert(!
isXRP(takerAmount.in) && !
isXRP(takerAmount.out));
339 if (
isXRP(takerAmount.in) ||
isXRP(takerAmount.out))
340 Throw<std::logic_error>(
"Bridging with XRP and an endpoint.");
371 bool have_bridge = offers_leg1.
step() && offers_leg2.
step();
372 bool have_direct = step_account(offers_direct, taker);
375 auto viewJ = ctx_.app.journal(
"View");
379 while (have_direct || have_bridge)
381 bool leg1_consumed =
false;
382 bool leg2_consumed =
false;
383 bool direct_consumed =
false;
385 auto const [use_direct, quality] = select_path(
386 have_direct, offers_direct, have_bridge, offers_leg1, offers_leg2);
390 if (taker.
reject(quality))
397 if (
auto stream = j_.
debug())
399 stream << count <<
" Direct:";
400 stream <<
" offer: " << offers_direct.
tip();
401 stream <<
" in: " << offers_direct.
tip().
amount().in;
402 stream <<
" out: " << offers_direct.
tip().
amount().out;
403 stream <<
" owner: " << offers_direct.
tip().
owner();
413 cross_result = taker.
cross(offers_direct.
tip());
417 if (dry_offer(view, offers_direct.
tip()))
419 direct_consumed =
true;
420 have_direct = step_account(offers_direct, taker);
425 if (
auto stream = j_.
debug())
441 stream << count <<
" Bridge:";
442 stream <<
" offer1: " << offers_leg1.
tip();
443 stream <<
" in: " << offers_leg1.
tip().
amount().in;
444 stream <<
" out: " << offers_leg1.
tip().
amount().out;
445 stream <<
" owner: " << offers_leg1.
tip().
owner();
446 stream <<
" funds: " << owner1_funds_before;
447 stream <<
" offer2: " << offers_leg2.
tip();
448 stream <<
" in: " << offers_leg2.
tip().
amount().in;
449 stream <<
" out: " << offers_leg2.
tip().
amount().out;
450 stream <<
" owner: " << offers_leg2.
tip().
owner();
451 stream <<
" funds: " << owner2_funds_before;
454 cross_result = taker.
cross(offers_leg1.
tip(), offers_leg2.
tip());
462 leg1_consumed = dry_offer(view, offers_leg1.
tip());
464 have_bridge &= offers_leg1.
step();
466 leg2_consumed = dry_offer(view, offers_leg2.
tip());
468 have_bridge &= offers_leg2.
step();
474 if (dry_offer(view, offers_leg1.
tip()))
476 leg1_consumed =
true;
477 have_bridge = (have_bridge && offers_leg1.
step());
479 if (dry_offer(view, offers_leg2.
tip()))
481 leg2_consumed =
true;
482 have_bridge = (have_bridge && offers_leg2.
step());
495 JLOG(j_.
debug()) <<
"The taker reports he's done during crossing!";
499 if (reachedOfferCrossingLimit(taker))
501 JLOG(j_.
debug()) <<
"The offer crossing limit has been exceeded!";
507 assert(direct_consumed || leg1_consumed || leg2_consumed);
509 if (!direct_consumed && !leg1_consumed && !leg2_consumed)
510 Throw<std::logic_error>(
511 "bridged crossing: nothing was fully consumed.");
518 CreateOffer::direct_cross(
535 bool have_offer = step_account(offers, taker);
541 bool direct_consumed =
false;
542 auto& offer(offers.tip());
545 if (taker.
reject(offer.quality()))
550 if (
auto stream = j_.
debug())
552 stream << count <<
" Direct:";
553 stream <<
" offer: " << offer;
554 stream <<
" in: " << offer.amount().in;
555 stream <<
" out: " << offer.amount().out;
556 stream <<
"quality: " << offer.quality();
557 stream <<
" owner: " << offer.owner();
564 ctx_.app.journal(
"View"));
567 cross_result = taker.
cross(offer);
571 if (dry_offer(view, offer))
573 direct_consumed =
true;
574 have_offer = step_account(offers, taker);
585 JLOG(j_.
debug()) <<
"The taker reports he's done during crossing!";
589 if (reachedOfferCrossingLimit(taker))
591 JLOG(j_.
debug()) <<
"The offer crossing limit has been exceeded!";
597 assert(direct_consumed);
599 if (!direct_consumed)
600 Throw<std::logic_error>(
601 "direct crossing: nothing was fully consumed.");
613 while (stream.step())
615 auto const& offer = stream.tip();
618 if (taker.
reject(offer.quality()))
622 if (offer.owner() != taker.
account())
634 CreateOffer::takerCross(
637 Amounts
const& takerAmount)
660 JLOG(j_.
debug()) <<
"Not crossing: taker is unfunded.";
666 if (cross_type_ == CrossType::IouToIou)
667 return bridged_cross(taker, sb, sbCancel, when);
669 return direct_cross(taker, sb, sbCancel, when);
673 JLOG(j_.
error()) <<
"Exception during offer crossing: " << e.
what();
679 CreateOffer::flowCross(
682 Amounts
const& takerAmount)
694 if (inStartBalance <= beast::zero)
697 JLOG(j_.
debug()) <<
"Not crossing: taker is unfunded.";
704 Rate gatewayXferRate{QUALITY_ONE};
709 if (gatewayXferRate.value != QUALITY_ONE)
714 takerAmount.in.issue(),
721 Quality threshold{takerAmount.out, sendMax};
730 if (sendMax > inStartBalance)
731 sendMax = inStartBalance;
738 if (!takerAmount.in.native() & !takerAmount.out.native())
741 path.emplace_back(boost::none,
xrpCurrency(), boost::none);
752 deliver =
STAmount{STAmount::cMaxNative};
759 takerAmount.out.
issue(),
760 STAmount::cMaxValue / 2,
761 STAmount::cMaxOffset};
765 auto const result =
flow(
780 for (
auto const& toRemove : result.removableOffers)
782 if (
auto otr = psb.
peek(keylet::offer(toRemove)))
784 if (
auto otr = psbCancel.
peek(keylet::offer(toRemove)))
789 auto afterCross = takerAmount;
795 if (takerInBalance <= beast::zero)
799 afterCross.in.clear();
800 afterCross.out.clear();
805 Quality{takerAmount.out, takerAmount.in}.rate()};
817 STAmount nonGatewayAmountIn = result.actualAmountIn;
818 if (gatewayXferRate.value != QUALITY_ONE)
820 result.actualAmountIn,
822 takerAmount.in.issue(),
825 afterCross.in -= nonGatewayAmountIn;
829 if (afterCross.in < beast::zero)
832 afterCross.in.
clear();
835 afterCross.in, rate, takerAmount.out.issue(),
true);
842 afterCross.out -= result.actualAmountOut;
843 assert(afterCross.out >= beast::zero);
844 if (afterCross.out < beast::zero)
845 afterCross.out.clear();
847 afterCross.out, rate, takerAmount.in.issue(),
true);
857 JLOG(j_.
error()) <<
"Exception during offer crossing: " << e.
what();
871 case SBoxCmp::dustDiff:
873 case SBoxCmp::offerDelDiff:
874 return "offer del diffs";
875 case SBoxCmp::xrpRound:
876 return "XRP round to zero";
893 CashFilter::treatZeroOfferAsDeletion,
905 if (
int const side =
diff.xrpRoundToZero())
907 char const*
const whichSide = side > 0 ?
"; Flow" :
"; Taker";
909 <<
"FlowCross: " << name <<
" different" << whichSide
911 return SBoxCmp::xrpRound;
914 c = SBoxCmp::dustDiff;
923 auto txID = txIdSs.
str();
927 c = SBoxCmp::offerDelDiff;
929 int sides =
diff.rmLhsDeletedOffers() ? 1 : 0;
930 sides |=
diff.rmRhsDeletedOffers() ? 2 : 0;
937 t =
"; Taker deleted more offers";
940 t =
"; Flow deleted more offers";
943 t =
"; Taker and Flow deleted different offers";
955 ss <<
"; common entries: " <<
diff.commonCount()
956 <<
"; Taker unique: " <<
diff.lhsOnlyCount()
957 <<
"; Flow unique: " <<
diff.rhsOnlyCount() << txID;
961 j.
stream(s) <<
"FlowCross: " << name <<
" different" << diffDesc;
977 Sandbox sbCancelTaker{&sbCancel};
978 auto const takerR = (!useFlowCross || doCompare)
979 ? takerCross(sbTaker, sbCancelTaker, takerAmount)
984 auto const flowR = (useFlowCross || doCompare)
985 ? flowCross(psbFlow, psbCancelFlow, takerAmount)
991 if (takerR.first != flowR.first)
994 j_.
warn() <<
"FlowCross: Offer cross tec codes different. tx: "
995 << ctx_.tx.getTransactionID();
998 (takerR.second.in == zero && flowR.second.in == zero) ||
999 (takerR.second.out == zero && flowR.second.out == zero))
1002 "Both Taker and Flow fully crossed",
1008 else if (takerR.second.in == zero && takerR.second.out == zero)
1010 char const* crossType =
1011 "Taker fully crossed, Flow partially crossed";
1012 if (flowR.second.in == takerAmount.in &&
1013 flowR.second.out == takerAmount.out)
1014 crossType =
"Taker fully crossed, Flow not crossed";
1018 else if (flowR.second.in == zero && flowR.second.out == zero)
1020 char const* crossType =
1021 "Taker partially crossed, Flow fully crossed";
1022 if (takerR.second.in == takerAmount.in &&
1023 takerR.second.out == takerAmount.out)
1024 crossType =
"Taker not crossed, Flow fully crossed";
1031 "FillOrKill offer", ctx_, sbCancelTaker, psbCancelFlow, j_);
1034 takerR.second.in == takerAmount.in &&
1035 flowR.second.in == takerAmount.in &&
1036 takerR.second.out == takerAmount.out &&
1037 flowR.second.out == takerAmount.out)
1039 char const* crossType =
"Neither Taker nor Flow crossed";
1043 takerR.second.in == takerAmount.in &&
1044 takerR.second.out == takerAmount.out)
1046 char const* crossType =
"Taker not crossed, Flow partially crossed";
1050 flowR.second.in == takerAmount.in &&
1051 flowR.second.out == takerAmount.out)
1053 char const* crossType =
"Taker partially crossed, Flow not crossed";
1059 "Partial cross offer", ctx_, sbTaker, psbFlow, j_);
1062 if (c <= SBoxCmp::dustDiff && takerR.second != flowR.second)
1064 c = SBoxCmp::dustDiff;
1068 if (!
diffIsDust(takerR.second.in, flowR.second.in) ||
1069 (!
diffIsDust(takerR.second.out, flowR.second.out)))
1071 char const* outSame =
"";
1072 if (takerR.second.out == flowR.second.out)
1073 outSame =
" but outs same";
1079 <<
". Taker in: " << takerR.second.in.getText()
1080 <<
"; Taker out: " << takerR.second.out.getText()
1081 <<
"; Flow in: " << flowR.second.in.getText()
1082 <<
"; Flow out: " << flowR.second.out.getText()
1083 <<
". tx: " << ctx_.tx.getTransactionID();
1084 onlyDust = ss.
str();
1087 <<
"FlowCross: Partial cross amounts different" << onlyDust;
1090 j_.
error() <<
"FlowCross cmp result: " << to_string(c);
1097 psbCancelFlow.apply(sbCancel);
1102 sbCancelTaker.apply(sbCancel);
1116 CreateOffer::preCompute()
1118 cross_type_ = CrossType::IouToIou;
1119 bool const pays_xrp = ctx_.tx.getFieldAmount(
sfTakerPays).native();
1120 bool const gets_xrp = ctx_.tx.getFieldAmount(
sfTakerGets).native();
1121 if (pays_xrp && !gets_xrp)
1122 cross_type_ = CrossType::IouToXrp;
1123 else if (gets_xrp && !pays_xrp)
1124 cross_type_ = CrossType::XrpToIou;
1126 return Transactor::preCompute();
1136 bool const bPassive(uTxFlags &
tfPassive);
1139 bool const bSell(uTxFlags &
tfSell);
1149 auto const uSequence = ctx_.tx.getSequence();
1154 auto uRate =
getRate(saTakerGets, saTakerPays);
1156 auto viewJ = ctx_.app.journal(
"View");
1163 auto const sleCancel =
1164 sb.
peek(keylet::offer(account_, *cancelSequence));
1171 JLOG(j_.
debug()) <<
"Create cancels order " << *cancelSequence;
1183 if (expiration && (ctx_.view().parentCloseTime() >= tp{d{*expiration}}))
1191 ctx_.view().rules().enabled(featureDepositPreauth)
1197 bool const bOpenLedger = ctx_.view().open();
1198 bool crossed =
false;
1200 if (result == tesSUCCESS)
1203 auto const& uPaysIssuerID = saTakerPays.getIssuer();
1204 auto const& uGetsIssuerID = saTakerGets.getIssuer();
1207 if (!
isXRP(uPaysIssuerID))
1209 auto const sle = sb.read(keylet::account(uPaysIssuerID));
1210 if (sle && sle->isFieldPresent(sfTickSize))
1211 uTickSize =
std::min(uTickSize, (*sle)[sfTickSize]);
1213 if (!
isXRP(uGetsIssuerID))
1215 auto const sle = sb.read(keylet::account(uGetsIssuerID));
1216 if (sle && sle->isFieldPresent(sfTickSize))
1217 uTickSize =
std::min(uTickSize, (*sle)[sfTickSize]);
1219 if (uTickSize < Quality::maxTickSize)
1222 Quality{saTakerGets, saTakerPays}.round(uTickSize).rate();
1230 saTakerPays =
multiply(saTakerGets, rate, saTakerPays.issue());
1235 saTakerGets =
divide(saTakerPays, rate, saTakerGets.issue());
1237 if (!saTakerGets || !saTakerPays)
1239 JLOG(j_.
debug()) <<
"Offer rounded to zero";
1240 return {result,
true};
1243 uRate =
getRate(saTakerGets, saTakerPays);
1247 Amounts
const takerAmount(saTakerGets, saTakerPays);
1252 Amounts place_offer;
1254 JLOG(j_.
debug()) <<
"Attempting cross: "
1255 <<
to_string(takerAmount.in.issue()) <<
" -> "
1258 if (
auto stream = j_.
trace())
1260 stream <<
" mode: " << (bPassive ?
"passive " :
"")
1261 << (bSell ?
"sell" :
"buy");
1262 stream <<
" in: " << format_amount(takerAmount.in);
1263 stream <<
" out: " << format_amount(takerAmount.out);
1266 std::tie(result, place_offer) = cross(sb, sbCancel, takerAmount);
1270 assert(result == tesSUCCESS ||
isTecClaim(result));
1272 if (
auto stream = j_.
trace())
1275 stream <<
" in: " << format_amount(place_offer.in);
1276 stream <<
" out: " << format_amount(place_offer.out);
1279 if (result == tecFAILED_PROCESSING && bOpenLedger)
1282 if (result != tesSUCCESS)
1285 return {result,
true};
1288 assert(saTakerGets.issue() == place_offer.in.issue());
1289 assert(saTakerPays.issue() == place_offer.out.issue());
1291 if (takerAmount != place_offer)
1296 if (place_offer.in < zero || place_offer.out < zero)
1298 JLOG(j_.
fatal()) <<
"Cross left offer negative!"
1299 <<
" in: " << format_amount(place_offer.in)
1300 <<
" out: " << format_amount(place_offer.out);
1304 if (place_offer.in == zero || place_offer.out == zero)
1306 JLOG(j_.
debug()) <<
"Offer fully crossed!";
1307 return {result,
true};
1313 saTakerPays = place_offer.out;
1314 saTakerGets = place_offer.in;
1317 assert(saTakerPays > zero && saTakerGets > zero);
1319 if (result != tesSUCCESS)
1322 return {result,
true};
1325 if (
auto stream = j_.
trace())
1327 stream <<
"Place" << (crossed ?
" remaining " :
" ") <<
"offer:";
1328 stream <<
" Pays: " << saTakerPays.getFullText();
1329 stream <<
" Gets: " << saTakerGets.getFullText();
1336 JLOG(j_.
trace()) <<
"Fill or Kill: offer killed";
1337 if (sb.rules().enabled(fix1578))
1344 if (bImmediateOrCancel)
1346 JLOG(j_.
trace()) <<
"Immediate or cancel: offer canceled";
1350 auto const sleCreator = sb.peek(keylet::account(account_));
1355 XRPAmount reserve = ctx_.view().fees().accountReserve(
1356 sleCreator->getFieldU32(sfOwnerCount) + 1);
1358 if (mPriorBalance < reserve)
1366 if (result != tesSUCCESS)
1371 return {result,
true};
1376 auto const offer_index = keylet::offer(account_, uSequence);
1379 auto const ownerNode = sb.dirInsert(
1385 <<
"final result: failed to add offer to owner's directory";
1392 JLOG(j_.
trace()) <<
"adding to book: " <<
to_string(saTakerPays.issue())
1393 <<
" : " <<
to_string(saTakerGets.issue());
1395 Book
const book{saTakerPays.issue(), saTakerGets.issue()};
1399 auto dir = keylet::quality(keylet::book(book), uRate);
1400 bool const bookExisted =
static_cast<bool>(sb.peek(dir));
1402 auto const bookNode = sb.dirAppend(dir, offer_index, [&](SLE::ref sle) {
1403 sle->setFieldH160(sfTakerPaysCurrency, saTakerPays.issue().currency);
1404 sle->setFieldH160(sfTakerPaysIssuer, saTakerPays.issue().account);
1405 sle->setFieldH160(sfTakerGetsCurrency, saTakerGets.issue().currency);
1406 sle->setFieldH160(sfTakerGetsIssuer, saTakerGets.issue().account);
1407 sle->setFieldU64(sfExchangeRate, uRate);
1412 JLOG(j_.
debug()) <<
"final result: failed to add offer to book";
1416 auto sleOffer = std::make_shared<SLE>(offer_index);
1417 sleOffer->setAccountID(sfAccount, account_);
1418 sleOffer->setFieldU32(sfSequence, uSequence);
1419 sleOffer->setFieldH256(sfBookDirectory, dir.key);
1420 sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
1421 sleOffer->setFieldAmount(sfTakerGets, saTakerGets);
1422 sleOffer->setFieldU64(sfOwnerNode, *ownerNode);
1423 sleOffer->setFieldU64(sfBookNode, *bookNode);
1425 sleOffer->setFieldU32(sfExpiration, *expiration);
1427 sleOffer->setFlag(lsfPassive);
1429 sleOffer->setFlag(lsfSell);
1430 sb.insert(sleOffer);
1433 ctx_.app.getOrderBookDB().addOrderBook(book);
1435 JLOG(j_.
debug()) <<
"final result: success";
1441 CreateOffer::doApply()
1450 Sandbox sbCancel(&ctx_.view());
1452 auto const result = applyGuts(sb, sbCancel);
1454 sb.
apply(ctx_.rawView());
1456 sbCancel.
apply(ctx_.rawView());
1457 return result.first;