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";
82 JLOG(j.debug()) <<
"Malformed offer: bad cancel sequence";
94 JLOG(j.debug()) <<
"Malformed offer: redundant (XRP for XRP)";
97 if (saTakerPays <= beast::zero || saTakerGets <= beast::zero)
99 JLOG(j.debug()) <<
"Malformed offer: bad amount";
103 auto const& uPaysIssuerID = saTakerPays.
getIssuer();
104 auto const& uPaysCurrency = saTakerPays.
getCurrency();
106 auto const& uGetsIssuerID = saTakerGets.
getIssuer();
107 auto const& uGetsCurrency = saTakerGets.
getCurrency();
109 if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID)
111 JLOG(j.debug()) <<
"Malformed offer: redundant (IOU for IOU)";
117 JLOG(j.debug()) <<
"Malformed offer: bad currency";
121 if (saTakerPays.
native() != !uPaysIssuerID ||
122 saTakerGets.
native() != !uGetsIssuerID)
124 JLOG(j.warn()) <<
"Malformed offer: bad issuer";
139 auto const& uPaysIssuerID = saTakerPays.getIssuer();
140 auto const& uPaysCurrency = saTakerPays.getCurrency();
142 auto const& uGetsIssuerID = saTakerGets.getIssuer();
157 JLOG(ctx.
j.
info()) <<
"Offer involves frozen asset";
166 <<
"delay: Offers must be at least partially funded.";
172 else if (cancelSequence && (uAccountSequence <= *cancelSequence))
174 JLOG(ctx.
j.
debug()) <<
"uAccountSequenceNext=" << uAccountSequence
175 <<
" uOfferSequence=" << *cancelSequence;
200 if (!saTakerPays.native())
202 auto result = checkAcceptAsset(
207 Issue(uPaysCurrency, uPaysIssuerID));
216 CreateOffer::checkAcceptAsset(
226 auto const issuerAccount = view.
read(keylet::account(issue.
account));
230 JLOG(j.
warn()) <<
"delay: can't receive IOUs from non-existent issuer: "
245 auto const trustLine =
256 bool const canonical_gt(
id > issue.
account);
258 bool const is_authorized(
264 <<
"delay: can't receive IOUs from issuer without auth.";
276 if (offer.fully_consumed())
283 ctx_.app.journal(
"View"));
284 return (amount <= beast::zero);
288 CreateOffer::select_path(
296 assert(have_direct || have_bridge);
302 Quality
const bridged_quality(
309 Quality
const direct_quality(direct.
tip().
quality());
311 if (bridged_quality < direct_quality)
321 CreateOffer::reachedOfferCrossingLimit(
Taker const& taker)
const
323 auto const crossings =
328 return crossings >= 850;
332 CreateOffer::bridged_cross(
340 assert(!
isXRP(takerAmount.in) && !
isXRP(takerAmount.out));
342 if (
isXRP(takerAmount.in) ||
isXRP(takerAmount.out))
343 Throw<std::logic_error>(
"Bridging with XRP and an endpoint.");
374 bool have_bridge = offers_leg1.
step() && offers_leg2.
step();
375 bool have_direct = step_account(offers_direct, taker);
378 auto viewJ = ctx_.app.journal(
"View");
382 while (have_direct || have_bridge)
384 bool leg1_consumed =
false;
385 bool leg2_consumed =
false;
386 bool direct_consumed =
false;
388 auto const [use_direct, quality] = select_path(
389 have_direct, offers_direct, have_bridge, offers_leg1, offers_leg2);
393 if (taker.
reject(quality))
400 if (
auto stream = j_.
debug())
402 stream << count <<
" Direct:";
403 stream <<
" offer: " << offers_direct.
tip();
404 stream <<
" in: " << offers_direct.
tip().
amount().in;
405 stream <<
" out: " << offers_direct.
tip().
amount().out;
406 stream <<
" owner: " << offers_direct.
tip().
owner();
416 cross_result = taker.
cross(offers_direct.
tip());
420 if (dry_offer(view, offers_direct.
tip()))
422 direct_consumed =
true;
423 have_direct = step_account(offers_direct, taker);
428 if (
auto stream = j_.
debug())
444 stream << count <<
" Bridge:";
445 stream <<
" offer1: " << offers_leg1.
tip();
446 stream <<
" in: " << offers_leg1.
tip().
amount().in;
447 stream <<
" out: " << offers_leg1.
tip().
amount().out;
448 stream <<
" owner: " << offers_leg1.
tip().
owner();
449 stream <<
" funds: " << owner1_funds_before;
450 stream <<
" offer2: " << offers_leg2.
tip();
451 stream <<
" in: " << offers_leg2.
tip().
amount().in;
452 stream <<
" out: " << offers_leg2.
tip().
amount().out;
453 stream <<
" owner: " << offers_leg2.
tip().
owner();
454 stream <<
" funds: " << owner2_funds_before;
457 cross_result = taker.
cross(offers_leg1.
tip(), offers_leg2.
tip());
465 leg1_consumed = dry_offer(view, offers_leg1.
tip());
467 have_bridge &= offers_leg1.
step();
469 leg2_consumed = dry_offer(view, offers_leg2.
tip());
471 have_bridge &= offers_leg2.
step();
477 if (dry_offer(view, offers_leg1.
tip()))
479 leg1_consumed =
true;
480 have_bridge = (have_bridge && offers_leg1.
step());
482 if (dry_offer(view, offers_leg2.
tip()))
484 leg2_consumed =
true;
485 have_bridge = (have_bridge && offers_leg2.
step());
498 JLOG(j_.
debug()) <<
"The taker reports he's done during crossing!";
502 if (reachedOfferCrossingLimit(taker))
504 JLOG(j_.
debug()) <<
"The offer crossing limit has been exceeded!";
510 assert(direct_consumed || leg1_consumed || leg2_consumed);
512 if (!direct_consumed && !leg1_consumed && !leg2_consumed)
513 Throw<std::logic_error>(
514 "bridged crossing: nothing was fully consumed.");
521 CreateOffer::direct_cross(
538 bool have_offer = step_account(offers, taker);
544 bool direct_consumed =
false;
545 auto& offer(offers.tip());
548 if (taker.
reject(offer.quality()))
553 if (
auto stream = j_.
debug())
555 stream << count <<
" Direct:";
556 stream <<
" offer: " << offer;
557 stream <<
" in: " << offer.amount().in;
558 stream <<
" out: " << offer.amount().out;
559 stream <<
"quality: " << offer.quality();
560 stream <<
" owner: " << offer.owner();
567 ctx_.app.journal(
"View"));
570 cross_result = taker.
cross(offer);
574 if (dry_offer(view, offer))
576 direct_consumed =
true;
577 have_offer = step_account(offers, taker);
588 JLOG(j_.
debug()) <<
"The taker reports he's done during crossing!";
592 if (reachedOfferCrossingLimit(taker))
594 JLOG(j_.
debug()) <<
"The offer crossing limit has been exceeded!";
600 assert(direct_consumed);
602 if (!direct_consumed)
603 Throw<std::logic_error>(
604 "direct crossing: nothing was fully consumed.");
616 while (stream.step())
618 auto const& offer = stream.tip();
621 if (taker.
reject(offer.quality()))
625 if (offer.owner() != taker.
account())
637 CreateOffer::takerCross(
640 Amounts
const& takerAmount)
663 JLOG(j_.
debug()) <<
"Not crossing: taker is unfunded.";
669 if (cross_type_ == CrossType::IouToIou)
670 return bridged_cross(taker, sb, sbCancel, when);
672 return direct_cross(taker, sb, sbCancel, when);
676 JLOG(j_.
error()) <<
"Exception during offer crossing: " << e.
what();
682 CreateOffer::flowCross(
685 Amounts
const& takerAmount)
697 if (inStartBalance <= beast::zero)
700 JLOG(j_.
debug()) <<
"Not crossing: taker is unfunded.";
707 Rate gatewayXferRate{QUALITY_ONE};
712 if (gatewayXferRate.value != QUALITY_ONE)
717 takerAmount.in.issue(),
724 Quality threshold{takerAmount.out, sendMax};
733 if (sendMax > inStartBalance)
734 sendMax = inStartBalance;
741 if (!takerAmount.in.native() & !takerAmount.out.native())
744 path.emplace_back(boost::none,
xrpCurrency(), boost::none);
755 deliver =
STAmount{STAmount::cMaxNative};
762 takerAmount.out.
issue(),
763 STAmount::cMaxValue / 2,
764 STAmount::cMaxOffset};
768 auto const result =
flow(
783 for (
auto const& toRemove : result.removableOffers)
785 if (
auto otr = psb.
peek(keylet::offer(toRemove)))
787 if (
auto otr = psbCancel.
peek(keylet::offer(toRemove)))
792 auto afterCross = takerAmount;
798 if (takerInBalance <= beast::zero)
802 afterCross.in.clear();
803 afterCross.out.clear();
808 Quality{takerAmount.out, takerAmount.in}.rate()};
820 STAmount nonGatewayAmountIn = result.actualAmountIn;
821 if (gatewayXferRate.value != QUALITY_ONE)
823 result.actualAmountIn,
825 takerAmount.in.issue(),
828 afterCross.in -= nonGatewayAmountIn;
832 if (afterCross.in < beast::zero)
835 afterCross.in.
clear();
838 afterCross.in, rate, takerAmount.out.issue(),
true);
845 afterCross.out -= result.actualAmountOut;
846 assert(afterCross.out >= beast::zero);
847 if (afterCross.out < beast::zero)
848 afterCross.out.clear();
850 afterCross.out, rate, takerAmount.in.issue(),
true);
860 JLOG(j_.
error()) <<
"Exception during offer crossing: " << e.
what();
874 case SBoxCmp::dustDiff:
876 case SBoxCmp::offerDelDiff:
877 return "offer del diffs";
878 case SBoxCmp::xrpRound:
879 return "XRP round to zero";
896 CashFilter::treatZeroOfferAsDeletion,
908 if (
int const side =
diff.xrpRoundToZero())
910 char const*
const whichSide = side > 0 ?
"; Flow" :
"; Taker";
912 <<
"FlowCross: " << name <<
" different" << whichSide
914 return SBoxCmp::xrpRound;
917 c = SBoxCmp::dustDiff;
926 auto txID = txIdSs.
str();
930 c = SBoxCmp::offerDelDiff;
932 int sides =
diff.rmLhsDeletedOffers() ? 1 : 0;
933 sides |=
diff.rmRhsDeletedOffers() ? 2 : 0;
940 t =
"; Taker deleted more offers";
943 t =
"; Flow deleted more offers";
946 t =
"; Taker and Flow deleted different offers";
958 ss <<
"; common entries: " <<
diff.commonCount()
959 <<
"; Taker unique: " <<
diff.lhsOnlyCount()
960 <<
"; Flow unique: " <<
diff.rhsOnlyCount() << txID;
964 j.
stream(s) <<
"FlowCross: " << name <<
" different" << diffDesc;
980 Sandbox sbCancelTaker{&sbCancel};
981 auto const takerR = (!useFlowCross || doCompare)
982 ? takerCross(sbTaker, sbCancelTaker, takerAmount)
987 auto const flowR = (useFlowCross || doCompare)
988 ? flowCross(psbFlow, psbCancelFlow, takerAmount)
994 if (takerR.first != flowR.first)
997 j_.
warn() <<
"FlowCross: Offer cross tec codes different. tx: "
998 << ctx_.tx.getTransactionID();
1001 (takerR.second.in == zero && flowR.second.in == zero) ||
1002 (takerR.second.out == zero && flowR.second.out == zero))
1005 "Both Taker and Flow fully crossed",
1011 else if (takerR.second.in == zero && takerR.second.out == zero)
1013 char const* crossType =
1014 "Taker fully crossed, Flow partially crossed";
1015 if (flowR.second.in == takerAmount.in &&
1016 flowR.second.out == takerAmount.out)
1017 crossType =
"Taker fully crossed, Flow not crossed";
1021 else if (flowR.second.in == zero && flowR.second.out == zero)
1023 char const* crossType =
1024 "Taker partially crossed, Flow fully crossed";
1025 if (takerR.second.in == takerAmount.in &&
1026 takerR.second.out == takerAmount.out)
1027 crossType =
"Taker not crossed, Flow fully crossed";
1034 "FillOrKill offer", ctx_, sbCancelTaker, psbCancelFlow, j_);
1037 takerR.second.in == takerAmount.in &&
1038 flowR.second.in == takerAmount.in &&
1039 takerR.second.out == takerAmount.out &&
1040 flowR.second.out == takerAmount.out)
1042 char const* crossType =
"Neither Taker nor Flow crossed";
1046 takerR.second.in == takerAmount.in &&
1047 takerR.second.out == takerAmount.out)
1049 char const* crossType =
"Taker not crossed, Flow partially crossed";
1053 flowR.second.in == takerAmount.in &&
1054 flowR.second.out == takerAmount.out)
1056 char const* crossType =
"Taker partially crossed, Flow not crossed";
1062 "Partial cross offer", ctx_, sbTaker, psbFlow, j_);
1065 if (c <= SBoxCmp::dustDiff && takerR.second != flowR.second)
1067 c = SBoxCmp::dustDiff;
1071 if (!
diffIsDust(takerR.second.in, flowR.second.in) ||
1072 (!
diffIsDust(takerR.second.out, flowR.second.out)))
1074 char const* outSame =
"";
1075 if (takerR.second.out == flowR.second.out)
1076 outSame =
" but outs same";
1082 <<
". Taker in: " << takerR.second.in.getText()
1083 <<
"; Taker out: " << takerR.second.out.getText()
1084 <<
"; Flow in: " << flowR.second.in.getText()
1085 <<
"; Flow out: " << flowR.second.out.getText()
1086 <<
". tx: " << ctx_.tx.getTransactionID();
1087 onlyDust = ss.
str();
1090 <<
"FlowCross: Partial cross amounts different" << onlyDust;
1093 j_.
error() <<
"FlowCross cmp result: " << to_string(c);
1100 psbCancelFlow.apply(sbCancel);
1105 sbCancelTaker.apply(sbCancel);
1119 CreateOffer::preCompute()
1121 cross_type_ = CrossType::IouToIou;
1122 bool const pays_xrp = ctx_.tx.getFieldAmount(
sfTakerPays).native();
1123 bool const gets_xrp = ctx_.tx.getFieldAmount(
sfTakerGets).native();
1124 if (pays_xrp && !gets_xrp)
1125 cross_type_ = CrossType::IouToXrp;
1126 else if (gets_xrp && !pays_xrp)
1127 cross_type_ = CrossType::XrpToIou;
1129 return Transactor::preCompute();
1139 bool const bPassive(uTxFlags &
tfPassive);
1142 bool const bSell(uTxFlags &
tfSell);
1151 auto const offerSequence = ctx_.tx.getSeqProxy().value();
1156 auto uRate =
getRate(saTakerGets, saTakerPays);
1158 auto viewJ = ctx_.app.journal(
"View");
1165 auto const sleCancel =
1166 sb.
peek(keylet::offer(account_, *cancelSequence));
1173 JLOG(j_.
debug()) <<
"Create cancels order " << *cancelSequence;
1185 if (expiration && (ctx_.view().parentCloseTime() >= tp{d{*expiration}}))
1193 ctx_.view().rules().enabled(featureDepositPreauth)
1199 bool const bOpenLedger = ctx_.view().open();
1200 bool crossed =
false;
1202 if (result == tesSUCCESS)
1205 auto const& uPaysIssuerID = saTakerPays.getIssuer();
1206 auto const& uGetsIssuerID = saTakerGets.getIssuer();
1209 if (!
isXRP(uPaysIssuerID))
1211 auto const sle = sb.read(keylet::account(uPaysIssuerID));
1212 if (sle && sle->isFieldPresent(sfTickSize))
1213 uTickSize =
std::min(uTickSize, (*sle)[sfTickSize]);
1215 if (!
isXRP(uGetsIssuerID))
1217 auto const sle = sb.read(keylet::account(uGetsIssuerID));
1218 if (sle && sle->isFieldPresent(sfTickSize))
1219 uTickSize =
std::min(uTickSize, (*sle)[sfTickSize]);
1221 if (uTickSize < Quality::maxTickSize)
1224 Quality{saTakerGets, saTakerPays}.round(uTickSize).rate();
1232 saTakerPays =
multiply(saTakerGets, rate, saTakerPays.issue());
1237 saTakerGets =
divide(saTakerPays, rate, saTakerGets.issue());
1239 if (!saTakerGets || !saTakerPays)
1241 JLOG(j_.
debug()) <<
"Offer rounded to zero";
1242 return {result,
true};
1245 uRate =
getRate(saTakerGets, saTakerPays);
1249 Amounts
const takerAmount(saTakerGets, saTakerPays);
1254 Amounts place_offer;
1256 JLOG(j_.
debug()) <<
"Attempting cross: "
1257 <<
to_string(takerAmount.in.issue()) <<
" -> "
1260 if (
auto stream = j_.
trace())
1262 stream <<
" mode: " << (bPassive ?
"passive " :
"")
1263 << (bSell ?
"sell" :
"buy");
1264 stream <<
" in: " << format_amount(takerAmount.in);
1265 stream <<
" out: " << format_amount(takerAmount.out);
1268 std::tie(result, place_offer) = cross(sb, sbCancel, takerAmount);
1272 assert(result == tesSUCCESS ||
isTecClaim(result));
1274 if (
auto stream = j_.
trace())
1277 stream <<
" in: " << format_amount(place_offer.in);
1278 stream <<
" out: " << format_amount(place_offer.out);
1281 if (result == tecFAILED_PROCESSING && bOpenLedger)
1284 if (result != tesSUCCESS)
1287 return {result,
true};
1290 assert(saTakerGets.issue() == place_offer.in.issue());
1291 assert(saTakerPays.issue() == place_offer.out.issue());
1293 if (takerAmount != place_offer)
1298 if (place_offer.in < zero || place_offer.out < zero)
1300 JLOG(j_.
fatal()) <<
"Cross left offer negative!"
1301 <<
" in: " << format_amount(place_offer.in)
1302 <<
" out: " << format_amount(place_offer.out);
1306 if (place_offer.in == zero || place_offer.out == zero)
1308 JLOG(j_.
debug()) <<
"Offer fully crossed!";
1309 return {result,
true};
1315 saTakerPays = place_offer.out;
1316 saTakerGets = place_offer.in;
1319 assert(saTakerPays > zero && saTakerGets > zero);
1321 if (result != tesSUCCESS)
1324 return {result,
true};
1327 if (
auto stream = j_.
trace())
1329 stream <<
"Place" << (crossed ?
" remaining " :
" ") <<
"offer:";
1330 stream <<
" Pays: " << saTakerPays.getFullText();
1331 stream <<
" Gets: " << saTakerGets.getFullText();
1338 JLOG(j_.
trace()) <<
"Fill or Kill: offer killed";
1339 if (sb.rules().enabled(fix1578))
1346 if (bImmediateOrCancel)
1348 JLOG(j_.
trace()) <<
"Immediate or cancel: offer canceled";
1352 auto const sleCreator = sb.peek(keylet::account(account_));
1357 XRPAmount reserve = ctx_.view().fees().accountReserve(
1358 sleCreator->getFieldU32(sfOwnerCount) + 1);
1360 if (mPriorBalance < reserve)
1368 if (result != tesSUCCESS)
1373 return {result,
true};
1378 auto const offer_index = keylet::offer(account_, offerSequence);
1381 auto const ownerNode = sb.dirInsert(
1387 <<
"final result: failed to add offer to owner's directory";
1394 JLOG(j_.
trace()) <<
"adding to book: " <<
to_string(saTakerPays.issue())
1395 <<
" : " <<
to_string(saTakerGets.issue());
1397 Book
const book{saTakerPays.issue(), saTakerGets.issue()};
1401 auto dir = keylet::quality(keylet::book(book), uRate);
1402 bool const bookExisted =
static_cast<bool>(sb.peek(dir));
1404 auto const bookNode = sb.dirAppend(dir, offer_index, [&](SLE::ref sle) {
1405 sle->setFieldH160(sfTakerPaysCurrency, saTakerPays.issue().currency);
1406 sle->setFieldH160(sfTakerPaysIssuer, saTakerPays.issue().account);
1407 sle->setFieldH160(sfTakerGetsCurrency, saTakerGets.issue().currency);
1408 sle->setFieldH160(sfTakerGetsIssuer, saTakerGets.issue().account);
1409 sle->setFieldU64(sfExchangeRate, uRate);
1414 JLOG(j_.
debug()) <<
"final result: failed to add offer to book";
1418 auto sleOffer = std::make_shared<SLE>(offer_index);
1419 sleOffer->setAccountID(sfAccount, account_);
1420 sleOffer->setFieldU32(sfSequence, offerSequence);
1421 sleOffer->setFieldH256(sfBookDirectory, dir.key);
1422 sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
1423 sleOffer->setFieldAmount(sfTakerGets, saTakerGets);
1424 sleOffer->setFieldU64(sfOwnerNode, *ownerNode);
1425 sleOffer->setFieldU64(sfBookNode, *bookNode);
1427 sleOffer->setFieldU32(sfExpiration, *expiration);
1429 sleOffer->setFlag(lsfPassive);
1431 sleOffer->setFlag(lsfSell);
1432 sb.insert(sleOffer);
1435 ctx_.app.getOrderBookDB().addOrderBook(book);
1437 JLOG(j_.
debug()) <<
"final result: success";
1443 CreateOffer::doApply()
1452 Sandbox sbCancel(&ctx_.view());
1454 auto const result = applyGuts(sb, sbCancel);
1456 sb.
apply(ctx_.rawView());
1458 sbCancel.
apply(ctx_.rawView());
1459 return result.first;