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 amount.native() ? amount.xrp() : beast::zero;
57 JLOG(j.debug()) <<
"Malformed transaction: Invalid flags set.";
64 if (bImmediateOrCancel && bFillOrKill)
66 JLOG(j.debug()) <<
"Malformed transaction: both IoC and FoK set.";
70 bool const bHaveExpiration(tx.isFieldPresent(
sfExpiration));
72 if (bHaveExpiration && (tx.getFieldU32(
sfExpiration) == 0))
74 JLOG(j.debug()) <<
"Malformed offer: bad expiration";
79 cancelSequence && *cancelSequence == 0)
81 JLOG(j.debug()) <<
"Malformed offer: bad cancel sequence";
93 JLOG(j.debug()) <<
"Malformed offer: redundant (XRP for XRP)";
96 if (saTakerPays <= beast::zero || saTakerGets <= beast::zero)
98 JLOG(j.debug()) <<
"Malformed offer: bad amount";
102 auto const& uPaysIssuerID = saTakerPays.
getIssuer();
103 auto const& uPaysCurrency = saTakerPays.
getCurrency();
105 auto const& uGetsIssuerID = saTakerGets.
getIssuer();
106 auto const& uGetsCurrency = saTakerGets.
getCurrency();
108 if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID)
110 JLOG(j.debug()) <<
"Malformed offer: redundant (IOU for IOU)";
116 JLOG(j.debug()) <<
"Malformed offer: bad currency";
120 if (saTakerPays.
native() != !uPaysIssuerID ||
121 saTakerGets.
native() != !uGetsIssuerID)
123 JLOG(j.warn()) <<
"Malformed offer: bad issuer";
138 auto const& uPaysIssuerID = saTakerPays.getIssuer();
139 auto const& uPaysCurrency = saTakerPays.getCurrency();
141 auto const& uGetsIssuerID = saTakerGets.getIssuer();
156 JLOG(ctx.
j.
info()) <<
"Offer involves frozen asset";
165 <<
"delay: Offers must be at least partially funded.";
171 else if (cancelSequence && (uAccountSequence <= *cancelSequence))
173 JLOG(ctx.
j.
debug()) <<
"uAccountSequenceNext=" << uAccountSequence
174 <<
" uOfferSequence=" << *cancelSequence;
199 if (!saTakerPays.native())
201 auto result = checkAcceptAsset(
206 Issue(uPaysCurrency, uPaysIssuerID));
215 CreateOffer::checkAcceptAsset(
225 auto const issuerAccount = view.
read(keylet::account(issue.
account));
229 JLOG(j.
warn()) <<
"delay: can't receive IOUs from non-existent issuer: "
244 auto const trustLine =
255 bool const canonical_gt(
id > issue.
account);
257 bool const is_authorized(
263 <<
"delay: can't receive IOUs from issuer without auth.";
275 if (offer.fully_consumed())
282 ctx_.app.journal(
"View"));
283 return (amount <= beast::zero);
287 CreateOffer::select_path(
295 assert(have_direct || have_bridge);
301 Quality
const bridged_quality(
308 Quality
const direct_quality(direct.
tip().
quality());
310 if (bridged_quality < direct_quality)
320 CreateOffer::reachedOfferCrossingLimit(
Taker const& taker)
const
322 auto const crossings =
327 return crossings >= 850;
331 CreateOffer::bridged_cross(
339 assert(!
isXRP(takerAmount.in) && !
isXRP(takerAmount.out));
341 if (
isXRP(takerAmount.in) ||
isXRP(takerAmount.out))
342 Throw<std::logic_error>(
"Bridging with XRP and an endpoint.");
373 bool have_bridge = offers_leg1.
step() && offers_leg2.
step();
374 bool have_direct = step_account(offers_direct, taker);
377 auto viewJ = ctx_.app.journal(
"View");
381 while (have_direct || have_bridge)
383 bool leg1_consumed =
false;
384 bool leg2_consumed =
false;
385 bool direct_consumed =
false;
387 auto const [use_direct, quality] = select_path(
388 have_direct, offers_direct, have_bridge, offers_leg1, offers_leg2);
392 if (taker.
reject(quality))
399 if (
auto stream = j_.
debug())
401 stream << count <<
" Direct:";
402 stream <<
" offer: " << offers_direct.
tip();
403 stream <<
" in: " << offers_direct.
tip().
amount().in;
404 stream <<
" out: " << offers_direct.
tip().
amount().out;
405 stream <<
" owner: " << offers_direct.
tip().
owner();
415 cross_result = taker.
cross(offers_direct.
tip());
419 if (dry_offer(view, offers_direct.
tip()))
421 direct_consumed =
true;
422 have_direct = step_account(offers_direct, taker);
427 if (
auto stream = j_.
debug())
443 stream << count <<
" Bridge:";
444 stream <<
" offer1: " << offers_leg1.
tip();
445 stream <<
" in: " << offers_leg1.
tip().
amount().in;
446 stream <<
" out: " << offers_leg1.
tip().
amount().out;
447 stream <<
" owner: " << offers_leg1.
tip().
owner();
448 stream <<
" funds: " << owner1_funds_before;
449 stream <<
" offer2: " << offers_leg2.
tip();
450 stream <<
" in: " << offers_leg2.
tip().
amount().in;
451 stream <<
" out: " << offers_leg2.
tip().
amount().out;
452 stream <<
" owner: " << offers_leg2.
tip().
owner();
453 stream <<
" funds: " << owner2_funds_before;
456 cross_result = taker.
cross(offers_leg1.
tip(), offers_leg2.
tip());
464 leg1_consumed = dry_offer(view, offers_leg1.
tip());
466 have_bridge &= offers_leg1.
step();
468 leg2_consumed = dry_offer(view, offers_leg2.
tip());
470 have_bridge &= offers_leg2.
step();
476 if (dry_offer(view, offers_leg1.
tip()))
478 leg1_consumed =
true;
479 have_bridge = (have_bridge && offers_leg1.
step());
481 if (dry_offer(view, offers_leg2.
tip()))
483 leg2_consumed =
true;
484 have_bridge = (have_bridge && offers_leg2.
step());
497 JLOG(j_.
debug()) <<
"The taker reports he's done during crossing!";
501 if (reachedOfferCrossingLimit(taker))
503 JLOG(j_.
debug()) <<
"The offer crossing limit has been exceeded!";
509 assert(direct_consumed || leg1_consumed || leg2_consumed);
511 if (!direct_consumed && !leg1_consumed && !leg2_consumed)
512 Throw<std::logic_error>(
513 "bridged crossing: nothing was fully consumed.");
520 CreateOffer::direct_cross(
537 bool have_offer = step_account(offers, taker);
543 bool direct_consumed =
false;
544 auto& offer(offers.tip());
547 if (taker.
reject(offer.quality()))
552 if (
auto stream = j_.
debug())
554 stream << count <<
" Direct:";
555 stream <<
" offer: " << offer;
556 stream <<
" in: " << offer.amount().in;
557 stream <<
" out: " << offer.amount().out;
558 stream <<
"quality: " << offer.quality();
559 stream <<
" owner: " << offer.owner();
566 ctx_.app.journal(
"View"));
569 cross_result = taker.
cross(offer);
573 if (dry_offer(view, offer))
575 direct_consumed =
true;
576 have_offer = step_account(offers, taker);
587 JLOG(j_.
debug()) <<
"The taker reports he's done during crossing!";
591 if (reachedOfferCrossingLimit(taker))
593 JLOG(j_.
debug()) <<
"The offer crossing limit has been exceeded!";
599 assert(direct_consumed);
601 if (!direct_consumed)
602 Throw<std::logic_error>(
603 "direct crossing: nothing was fully consumed.");
615 while (stream.step())
617 auto const& offer = stream.tip();
620 if (taker.
reject(offer.quality()))
624 if (offer.owner() != taker.
account())
636 CreateOffer::takerCross(
639 Amounts
const& takerAmount)
662 JLOG(j_.
debug()) <<
"Not crossing: taker is unfunded.";
668 if (cross_type_ == CrossType::IouToIou)
669 return bridged_cross(taker, sb, sbCancel, when);
671 return direct_cross(taker, sb, sbCancel, when);
675 JLOG(j_.
error()) <<
"Exception during offer crossing: " << e.
what();
681 CreateOffer::flowCross(
684 Amounts
const& takerAmount)
696 if (inStartBalance <= beast::zero)
699 JLOG(j_.
debug()) <<
"Not crossing: taker is unfunded.";
706 Rate gatewayXferRate{QUALITY_ONE};
711 if (gatewayXferRate.value != QUALITY_ONE)
716 takerAmount.in.issue(),
723 Quality threshold{takerAmount.out, sendMax};
732 if (sendMax > inStartBalance)
733 sendMax = inStartBalance;
740 if (!takerAmount.in.native() & !takerAmount.out.native())
743 path.emplace_back(boost::none,
xrpCurrency(), boost::none);
754 deliver =
STAmount{STAmount::cMaxNative};
761 takerAmount.out.
issue(),
762 STAmount::cMaxValue / 2,
763 STAmount::cMaxOffset};
767 auto const result =
flow(
782 for (
auto const& toRemove : result.removableOffers)
784 if (
auto otr = psb.
peek(keylet::offer(toRemove)))
786 if (
auto otr = psbCancel.
peek(keylet::offer(toRemove)))
791 auto afterCross = takerAmount;
797 if (takerInBalance <= beast::zero)
801 afterCross.in.clear();
802 afterCross.out.clear();
807 Quality{takerAmount.out, takerAmount.in}.rate()};
819 STAmount nonGatewayAmountIn = result.actualAmountIn;
820 if (gatewayXferRate.value != QUALITY_ONE)
822 result.actualAmountIn,
824 takerAmount.in.issue(),
827 afterCross.in -= nonGatewayAmountIn;
831 if (afterCross.in < beast::zero)
834 afterCross.in.
clear();
837 afterCross.in, rate, takerAmount.out.issue(),
true);
844 afterCross.out -= result.actualAmountOut;
845 assert(afterCross.out >= beast::zero);
846 if (afterCross.out < beast::zero)
847 afterCross.out.clear();
849 afterCross.out, rate, takerAmount.in.issue(),
true);
859 JLOG(j_.
error()) <<
"Exception during offer crossing: " << e.
what();
873 case SBoxCmp::dustDiff:
875 case SBoxCmp::offerDelDiff:
876 return "offer del diffs";
877 case SBoxCmp::xrpRound:
878 return "XRP round to zero";
895 CashFilter::treatZeroOfferAsDeletion,
907 if (
int const side =
diff.xrpRoundToZero())
909 char const*
const whichSide = side > 0 ?
"; Flow" :
"; Taker";
911 <<
"FlowCross: " << name <<
" different" << whichSide
913 return SBoxCmp::xrpRound;
916 c = SBoxCmp::dustDiff;
925 auto txID = txIdSs.
str();
929 c = SBoxCmp::offerDelDiff;
931 int sides =
diff.rmLhsDeletedOffers() ? 1 : 0;
932 sides |=
diff.rmRhsDeletedOffers() ? 2 : 0;
939 t =
"; Taker deleted more offers";
942 t =
"; Flow deleted more offers";
945 t =
"; Taker and Flow deleted different offers";
957 ss <<
"; common entries: " <<
diff.commonCount()
958 <<
"; Taker unique: " <<
diff.lhsOnlyCount()
959 <<
"; Flow unique: " <<
diff.rhsOnlyCount() << txID;
963 j.
stream(s) <<
"FlowCross: " << name <<
" different" << diffDesc;
979 Sandbox sbCancelTaker{&sbCancel};
980 auto const takerR = (!useFlowCross || doCompare)
981 ? takerCross(sbTaker, sbCancelTaker, takerAmount)
986 auto const flowR = (useFlowCross || doCompare)
987 ? flowCross(psbFlow, psbCancelFlow, takerAmount)
993 if (takerR.first != flowR.first)
996 j_.
warn() <<
"FlowCross: Offer cross tec codes different. tx: "
997 << ctx_.tx.getTransactionID();
1000 (takerR.second.in == zero && flowR.second.in == zero) ||
1001 (takerR.second.out == zero && flowR.second.out == zero))
1004 "Both Taker and Flow fully crossed",
1010 else if (takerR.second.in == zero && takerR.second.out == zero)
1012 char const* crossType =
1013 "Taker fully crossed, Flow partially crossed";
1014 if (flowR.second.in == takerAmount.in &&
1015 flowR.second.out == takerAmount.out)
1016 crossType =
"Taker fully crossed, Flow not crossed";
1020 else if (flowR.second.in == zero && flowR.second.out == zero)
1022 char const* crossType =
1023 "Taker partially crossed, Flow fully crossed";
1024 if (takerR.second.in == takerAmount.in &&
1025 takerR.second.out == takerAmount.out)
1026 crossType =
"Taker not crossed, Flow fully crossed";
1033 "FillOrKill offer", ctx_, sbCancelTaker, psbCancelFlow, j_);
1036 takerR.second.in == takerAmount.in &&
1037 flowR.second.in == takerAmount.in &&
1038 takerR.second.out == takerAmount.out &&
1039 flowR.second.out == takerAmount.out)
1041 char const* crossType =
"Neither Taker nor Flow crossed";
1045 takerR.second.in == takerAmount.in &&
1046 takerR.second.out == takerAmount.out)
1048 char const* crossType =
"Taker not crossed, Flow partially crossed";
1052 flowR.second.in == takerAmount.in &&
1053 flowR.second.out == takerAmount.out)
1055 char const* crossType =
"Taker partially crossed, Flow not crossed";
1061 "Partial cross offer", ctx_, sbTaker, psbFlow, j_);
1064 if (c <= SBoxCmp::dustDiff && takerR.second != flowR.second)
1066 c = SBoxCmp::dustDiff;
1070 if (!
diffIsDust(takerR.second.in, flowR.second.in) ||
1071 (!
diffIsDust(takerR.second.out, flowR.second.out)))
1073 char const* outSame =
"";
1074 if (takerR.second.out == flowR.second.out)
1075 outSame =
" but outs same";
1081 <<
". Taker in: " << takerR.second.in.getText()
1082 <<
"; Taker out: " << takerR.second.out.getText()
1083 <<
"; Flow in: " << flowR.second.in.getText()
1084 <<
"; Flow out: " << flowR.second.out.getText()
1085 <<
". tx: " << ctx_.tx.getTransactionID();
1086 onlyDust = ss.
str();
1089 <<
"FlowCross: Partial cross amounts different" << onlyDust;
1092 j_.
error() <<
"FlowCross cmp result: " << to_string(c);
1099 psbCancelFlow.apply(sbCancel);
1104 sbCancelTaker.apply(sbCancel);
1118 CreateOffer::preCompute()
1120 cross_type_ = CrossType::IouToIou;
1121 bool const pays_xrp = ctx_.tx.getFieldAmount(
sfTakerPays).native();
1122 bool const gets_xrp = ctx_.tx.getFieldAmount(
sfTakerGets).native();
1123 if (pays_xrp && !gets_xrp)
1124 cross_type_ = CrossType::IouToXrp;
1125 else if (gets_xrp && !pays_xrp)
1126 cross_type_ = CrossType::XrpToIou;
1128 return Transactor::preCompute();
1138 bool const bPassive(uTxFlags &
tfPassive);
1141 bool const bSell(uTxFlags &
tfSell);
1150 auto const offerSequence = ctx_.tx.getSeqProxy().value();
1155 auto uRate =
getRate(saTakerGets, saTakerPays);
1157 auto viewJ = ctx_.app.journal(
"View");
1164 auto const sleCancel =
1165 sb.
peek(keylet::offer(account_, *cancelSequence));
1172 JLOG(j_.
debug()) <<
"Create cancels order " << *cancelSequence;
1184 if (expiration && (ctx_.view().parentCloseTime() >= tp{d{*expiration}}))
1192 ctx_.view().rules().enabled(featureDepositPreauth)
1198 bool const bOpenLedger = ctx_.view().open();
1199 bool crossed =
false;
1201 if (result == tesSUCCESS)
1204 auto const& uPaysIssuerID = saTakerPays.getIssuer();
1205 auto const& uGetsIssuerID = saTakerGets.getIssuer();
1208 if (!
isXRP(uPaysIssuerID))
1210 auto const sle = sb.read(keylet::account(uPaysIssuerID));
1211 if (sle && sle->isFieldPresent(sfTickSize))
1212 uTickSize =
std::min(uTickSize, (*sle)[sfTickSize]);
1214 if (!
isXRP(uGetsIssuerID))
1216 auto const sle = sb.read(keylet::account(uGetsIssuerID));
1217 if (sle && sle->isFieldPresent(sfTickSize))
1218 uTickSize =
std::min(uTickSize, (*sle)[sfTickSize]);
1220 if (uTickSize < Quality::maxTickSize)
1223 Quality{saTakerGets, saTakerPays}.round(uTickSize).rate();
1231 saTakerPays =
multiply(saTakerGets, rate, saTakerPays.issue());
1236 saTakerGets =
divide(saTakerPays, rate, saTakerGets.issue());
1238 if (!saTakerGets || !saTakerPays)
1240 JLOG(j_.
debug()) <<
"Offer rounded to zero";
1241 return {result,
true};
1244 uRate =
getRate(saTakerGets, saTakerPays);
1248 Amounts
const takerAmount(saTakerGets, saTakerPays);
1253 Amounts place_offer;
1255 JLOG(j_.
debug()) <<
"Attempting cross: "
1256 <<
to_string(takerAmount.in.issue()) <<
" -> "
1259 if (
auto stream = j_.
trace())
1261 stream <<
" mode: " << (bPassive ?
"passive " :
"")
1262 << (bSell ?
"sell" :
"buy");
1263 stream <<
" in: " << format_amount(takerAmount.in);
1264 stream <<
" out: " << format_amount(takerAmount.out);
1267 std::tie(result, place_offer) = cross(sb, sbCancel, takerAmount);
1271 assert(result == tesSUCCESS ||
isTecClaim(result));
1273 if (
auto stream = j_.
trace())
1276 stream <<
" in: " << format_amount(place_offer.in);
1277 stream <<
" out: " << format_amount(place_offer.out);
1280 if (result == tecFAILED_PROCESSING && bOpenLedger)
1283 if (result != tesSUCCESS)
1286 return {result,
true};
1289 assert(saTakerGets.issue() == place_offer.in.issue());
1290 assert(saTakerPays.issue() == place_offer.out.issue());
1292 if (takerAmount != place_offer)
1297 if (place_offer.in < zero || place_offer.out < zero)
1299 JLOG(j_.
fatal()) <<
"Cross left offer negative!"
1300 <<
" in: " << format_amount(place_offer.in)
1301 <<
" out: " << format_amount(place_offer.out);
1305 if (place_offer.in == zero || place_offer.out == zero)
1307 JLOG(j_.
debug()) <<
"Offer fully crossed!";
1308 return {result,
true};
1314 saTakerPays = place_offer.out;
1315 saTakerGets = place_offer.in;
1318 assert(saTakerPays > zero && saTakerGets > zero);
1320 if (result != tesSUCCESS)
1323 return {result,
true};
1326 if (
auto stream = j_.
trace())
1328 stream <<
"Place" << (crossed ?
" remaining " :
" ") <<
"offer:";
1329 stream <<
" Pays: " << saTakerPays.getFullText();
1330 stream <<
" Gets: " << saTakerGets.getFullText();
1337 JLOG(j_.
trace()) <<
"Fill or Kill: offer killed";
1338 if (sb.rules().enabled(fix1578))
1345 if (bImmediateOrCancel)
1347 JLOG(j_.
trace()) <<
"Immediate or cancel: offer canceled";
1351 auto const sleCreator = sb.peek(keylet::account(account_));
1356 XRPAmount reserve = ctx_.view().fees().accountReserve(
1357 sleCreator->getFieldU32(sfOwnerCount) + 1);
1359 if (mPriorBalance < reserve)
1367 if (result != tesSUCCESS)
1372 return {result,
true};
1377 auto const offer_index = keylet::offer(account_, offerSequence);
1380 auto const ownerNode = sb.dirInsert(
1386 <<
"final result: failed to add offer to owner's directory";
1393 JLOG(j_.
trace()) <<
"adding to book: " <<
to_string(saTakerPays.issue())
1394 <<
" : " <<
to_string(saTakerGets.issue());
1396 Book
const book{saTakerPays.issue(), saTakerGets.issue()};
1400 auto dir = keylet::quality(keylet::book(book), uRate);
1401 bool const bookExisted =
static_cast<bool>(sb.peek(dir));
1403 auto const bookNode = sb.dirAppend(dir, offer_index, [&](SLE::ref sle) {
1404 sle->setFieldH160(sfTakerPaysCurrency, saTakerPays.issue().currency);
1405 sle->setFieldH160(sfTakerPaysIssuer, saTakerPays.issue().account);
1406 sle->setFieldH160(sfTakerGetsCurrency, saTakerGets.issue().currency);
1407 sle->setFieldH160(sfTakerGetsIssuer, saTakerGets.issue().account);
1408 sle->setFieldU64(sfExchangeRate, uRate);
1413 JLOG(j_.
debug()) <<
"final result: failed to add offer to book";
1417 auto sleOffer = std::make_shared<SLE>(offer_index);
1418 sleOffer->setAccountID(sfAccount, account_);
1419 sleOffer->setFieldU32(sfSequence, offerSequence);
1420 sleOffer->setFieldH256(sfBookDirectory, dir.key);
1421 sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
1422 sleOffer->setFieldAmount(sfTakerGets, saTakerGets);
1423 sleOffer->setFieldU64(sfOwnerNode, *ownerNode);
1424 sleOffer->setFieldU64(sfBookNode, *bookNode);
1426 sleOffer->setFieldU32(sfExpiration, *expiration);
1428 sleOffer->setFlag(lsfPassive);
1430 sleOffer->setFlag(lsfSell);
1431 sb.insert(sleOffer);
1434 ctx_.app.getOrderBookDB().addOrderBook(book);
1436 JLOG(j_.
debug()) <<
"final result: success";
1442 CreateOffer::doApply()
1451 Sandbox sbCancel(&ctx_.view());
1453 auto const result = applyGuts(sb, sbCancel);
1455 sb.
apply(ctx_.rawView());
1457 sbCancel.
apply(ctx_.rawView());
1458 return result.first;