20 #include <ripple/app/tx/impl/CreateOffer.h>
21 #include <ripple/app/ledger/OrderBookDB.h>
22 #include <ripple/app/paths/Flow.h>
23 #include <ripple/ledger/CashDiff.h>
24 #include <ripple/ledger/PaymentSandbox.h>
25 #include <ripple/protocol/Feature.h>
26 #include <ripple/protocol/st.h>
27 #include <ripple/protocol/Quality.h>
28 #include <ripple/beast/utility/WrappedSink.h>
37 return saTakerGets.native() ?
38 saTakerGets.xrp() : beast::zero;
56 "Malformed transaction: Invalid flags set.";
63 if (bImmediateOrCancel && bFillOrKill)
66 "Malformed transaction: both IoC and FoK set.";
70 bool const bHaveExpiration (tx.isFieldPresent (
sfExpiration));
72 if (bHaveExpiration && (tx.getFieldU32 (
sfExpiration) == 0))
75 "Malformed offer: bad expiration";
84 "Malformed offer: bad cancel sequence";
97 "Malformed offer: redundant (XRP for XRP)";
100 if (saTakerPays <= beast::zero || saTakerGets <= beast::zero)
103 "Malformed offer: bad amount";
107 auto const& uPaysIssuerID = saTakerPays.
getIssuer ();
108 auto const& uPaysCurrency = saTakerPays.
getCurrency ();
110 auto const& uGetsIssuerID = saTakerGets.
getIssuer ();
111 auto const& uGetsCurrency = saTakerGets.
getCurrency ();
113 if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID)
116 "Malformed offer: redundant (IOU for IOU)";
123 "Malformed offer: bad currency";
127 if (saTakerPays.
native () != !uPaysIssuerID ||
128 saTakerGets.
native () != !uGetsIssuerID)
131 "Malformed offer: bad issuer";
146 auto const& uPaysIssuerID = saTakerPays.getIssuer();
147 auto const& uPaysCurrency = saTakerPays.getCurrency();
149 auto const& uGetsIssuerID = saTakerGets.getIssuer();
164 JLOG(ctx.
j.
info()) <<
165 "Offer involves frozen asset";
173 "delay: Offers must be at least partially funded.";
179 else if (cancelSequence && (uAccountSequence <= *cancelSequence))
182 "uAccountSequenceNext=" << uAccountSequence <<
183 " uOfferSequence=" << *cancelSequence;
209 if (!saTakerPays.native())
211 auto result = checkAcceptAsset(ctx.view, ctx.flags,
212 id, ctx.j, Issue(uPaysCurrency, uPaysIssuerID));
221 CreateOffer::checkAcceptAsset(
ReadView const& view,
228 auto const issuerAccount = view.
read(
229 keylet::account(issue.
account));
234 "delay: can't receive IOUs from non-existent issuer: " <<
251 auto const trustLine = view.
read(
264 bool const canonical_gt (
id > issue.
account);
266 bool const is_authorized ((*trustLine)[
sfFlags] &
272 "delay: can't receive IOUs from issuer without auth.";
286 if (offer.fully_consumed ())
290 return (amount <= beast::zero);
294 CreateOffer::select_path (
299 assert (have_direct || have_bridge);
312 Quality
const direct_quality (direct.
tip ().
quality ());
314 if (bridged_quality < direct_quality)
324 CreateOffer::reachedOfferCrossingLimit (
Taker const& taker)
const
326 auto const crossings =
332 return crossings >= 850;
336 CreateOffer::bridged_cross (
344 assert (!
isXRP (takerAmount.in) && !
isXRP (takerAmount.out));
346 if (
isXRP (takerAmount.in) ||
isXRP (takerAmount.out))
347 Throw<std::logic_error> (
"Bridging with XRP and an endpoint.");
351 when, stepCounter_, j_);
355 when, stepCounter_, j_);
359 when, stepCounter_, j_);
366 bool have_bridge = offers_leg1.
step () && offers_leg2.
step ();
367 bool have_direct = step_account (offers_direct, taker);
370 auto viewJ = ctx_.app.journal (
"View");
374 while (have_direct || have_bridge)
376 bool leg1_consumed =
false;
377 bool leg2_consumed =
false;
378 bool direct_consumed =
false;
380 auto const [use_direct, quality] = select_path (
381 have_direct, offers_direct,
382 have_bridge, offers_leg1, offers_leg2);
387 if (taker.
reject(quality))
394 if (
auto stream = j_.
debug())
396 stream << count <<
" Direct:";
397 stream <<
" offer: " << offers_direct.
tip ();
398 stream <<
" in: " << offers_direct.
tip ().
amount().in;
399 stream <<
" out: " << offers_direct.
tip ().
amount ().out;
400 stream <<
" owner: " << offers_direct.
tip ().
owner ();
407 cross_result = taker.
cross(offers_direct.
tip ());
411 if (dry_offer (view, offers_direct.
tip ()))
413 direct_consumed =
true;
414 have_direct = step_account (offers_direct, taker);
419 if (
auto stream = j_.
debug())
431 stream << count <<
" Bridge:";
432 stream <<
" offer1: " << offers_leg1.
tip ();
433 stream <<
" in: " << offers_leg1.
tip ().
amount().in;
434 stream <<
" out: " << offers_leg1.
tip ().
amount ().out;
435 stream <<
" owner: " << offers_leg1.
tip ().
owner ();
436 stream <<
" funds: " << owner1_funds_before;
437 stream <<
" offer2: " << offers_leg2.
tip ();
438 stream <<
" in: " << offers_leg2.
tip ().
amount ().in;
439 stream <<
" out: " << offers_leg2.
tip ().
amount ().out;
440 stream <<
" owner: " << offers_leg2.
tip ().
owner ();
441 stream <<
" funds: " << owner2_funds_before;
444 cross_result = taker.
cross(offers_leg1.
tip (), offers_leg2.
tip ());
452 leg1_consumed = dry_offer (view, offers_leg1.
tip());
454 have_bridge &= offers_leg1.
step();
456 leg2_consumed = dry_offer (view, offers_leg2.
tip());
458 have_bridge &= offers_leg2.
step();
464 if (dry_offer (view, offers_leg1.
tip ()))
466 leg1_consumed =
true;
467 have_bridge = (have_bridge && offers_leg1.
step ());
469 if (dry_offer (view, offers_leg2.
tip ()))
471 leg2_consumed =
true;
472 have_bridge = (have_bridge && offers_leg2.
step ());
485 JLOG(j_.
debug()) <<
"The taker reports he's done during crossing!";
489 if (reachedOfferCrossingLimit (taker))
491 JLOG(j_.
debug()) <<
"The offer crossing limit has been exceeded!";
497 assert (direct_consumed || leg1_consumed || leg2_consumed);
499 if (!direct_consumed && !leg1_consumed && !leg2_consumed)
500 Throw<std::logic_error> (
"bridged crossing: nothing was fully consumed.");
507 CreateOffer::direct_cross (
516 when, stepCounter_, j_);
521 bool have_offer = step_account (offers, taker);
527 bool direct_consumed =
false;
528 auto& offer (offers.tip());
531 if (taker.
reject (offer.quality()))
536 if (
auto stream = j_.
debug())
538 stream << count <<
" Direct:";
539 stream <<
" offer: " << offer;
540 stream <<
" in: " << offer.amount ().in;
541 stream <<
" out: " << offer.amount ().out;
542 stream <<
"quality: " << offer.quality();
543 stream <<
" owner: " << offer.owner ();
546 ctx_.app.journal (
"View"));
549 cross_result = taker.
cross (offer);
553 if (dry_offer (view, offer))
555 direct_consumed =
true;
556 have_offer = step_account (offers, taker);
567 JLOG(j_.
debug()) <<
"The taker reports he's done during crossing!";
571 if (reachedOfferCrossingLimit (taker))
573 JLOG(j_.
debug()) <<
"The offer crossing limit has been exceeded!";
579 assert (direct_consumed);
581 if (!direct_consumed)
582 Throw<std::logic_error> (
"direct crossing: nothing was fully consumed.");
594 while (stream.step ())
596 auto const& offer = stream.tip ();
599 if (taker.
reject (offer.quality ()))
603 if (offer.owner () != taker.
account ())
615 CreateOffer::takerCross (
618 Amounts
const& takerAmount)
624 Taker taker (cross_type_, sb, account_, takerAmount,
637 "Not crossing: taker is unfunded.";
643 if (cross_type_ == CrossType::IouToIou)
644 return bridged_cross (taker, sb, sbCancel, when);
646 return direct_cross (taker, sb, sbCancel, when);
651 "Exception during offer crossing: " << e.
what ();
657 CreateOffer::flowCross (
660 Amounts
const& takerAmount)
672 if (inStartBalance <= beast::zero)
676 "Not crossing: taker is unfunded.";
683 Rate gatewayXferRate {QUALITY_ONE};
688 if (gatewayXferRate.value != QUALITY_ONE)
691 gatewayXferRate, takerAmount.in.issue(),
true);
697 Quality threshold { takerAmount.out, sendMax };
706 if (sendMax > inStartBalance)
707 sendMax = inStartBalance;
714 if (!takerAmount.in.native() & !takerAmount.out.native())
717 path.emplace_back (boost::none,
xrpCurrency(), boost::none);
728 deliver =
STAmount { STAmount::cMaxNative };
735 STAmount::cMaxValue / 2, STAmount::cMaxOffset };
739 auto const result =
flow (psb, deliver, account_, account_,
749 for (
auto const& toRemove : result.removableOffers)
751 if (
auto otr = psb.
peek (keylet::offer (toRemove)))
753 if (
auto otr = psbCancel.
peek (keylet::offer (toRemove)))
758 auto afterCross = takerAmount;
764 if (takerInBalance <= beast::zero)
768 afterCross.in.clear();
769 afterCross.out.clear();
774 Quality{takerAmount.out, takerAmount.in}.rate() };
786 STAmount nonGatewayAmountIn = result.actualAmountIn;
787 if (gatewayXferRate.value != QUALITY_ONE)
788 nonGatewayAmountIn =
divideRound (result.actualAmountIn,
789 gatewayXferRate, takerAmount.in.issue(),
true);
791 afterCross.in -= nonGatewayAmountIn;
795 if (afterCross.in < beast::zero)
798 afterCross.in.
clear();
800 afterCross.out =
divRound (afterCross.in,
801 rate, takerAmount.out.issue(),
true);
808 afterCross.out -= result.actualAmountOut;
809 assert (afterCross.out >= beast::zero);
810 if (afterCross.out < beast::zero)
811 afterCross.out.clear();
812 afterCross.in =
mulRound (afterCross.out,
813 rate, takerAmount.in.issue(),
true);
824 "Exception during offer crossing: " << e.
what ();
844 case SBoxCmp::dustDiff:
846 case SBoxCmp::offerDelDiff:
847 return "offer del diffs";
848 case SBoxCmp::xrpRound:
849 return "XRP round to zero";
862 CashFilter::treatZeroOfferAsDeletion, viewTaker,
863 CashFilter::none, viewFlow);
872 if (
int const side =
diff.xrpRoundToZero())
874 char const*
const whichSide = side > 0 ?
"; Flow" :
"; Taker";
876 whichSide <<
" XRP rounded to zero. tx: " <<
878 return SBoxCmp::xrpRound;
881 c = SBoxCmp::dustDiff;
890 auto txID = txIdSs.
str();
894 c = SBoxCmp::offerDelDiff;
896 int sides =
diff.rmLhsDeletedOffers() ? 1 : 0;
897 sides |=
diff.rmRhsDeletedOffers() ? 2 : 0;
903 case 1: t =
"; Taker deleted more offers";
break;
904 case 2: t =
"; Flow deleted more offers";
break;
905 case 3: t =
"; Taker and Flow deleted different offers";
break;
915 ss <<
"; common entries: " <<
diff.commonCount()
916 <<
"; Taker unique: " <<
diff.lhsOnlyCount()
917 <<
"; Flow unique: " <<
diff.rhsOnlyCount() << txID;
921 j.
stream (s) <<
"FlowCross: " << name <<
" different" << diffDesc;
930 Amounts
const& takerAmount)
940 Sandbox sbCancelTaker { &sbCancel };
941 auto const takerR = (!useFlowCross || doCompare)
942 ? takerCross (sbTaker, sbCancelTaker, takerAmount)
947 auto const flowR = (useFlowCross || doCompare)
948 ? flowCross (psbFlow, psbCancelFlow, takerAmount)
954 if (takerR.first != flowR.first)
957 j_.
warn() <<
"FlowCross: Offer cross tec codes different. tx: "
958 << ctx_.tx.getTransactionID();
960 else if ((takerR.second.in == zero && flowR.second.in == zero) ||
961 (takerR.second.out == zero && flowR.second.out == zero))
964 ctx_, sbTaker, psbFlow, j_);
966 else if (takerR.second.in == zero && takerR.second.out == zero)
968 char const * crossType =
"Taker fully crossed, Flow partially crossed";
969 if (flowR.second.in == takerAmount.in &&
970 flowR.second.out == takerAmount.out)
971 crossType =
"Taker fully crossed, Flow not crossed";
975 else if (flowR.second.in == zero && flowR.second.out == zero)
977 char const * crossType =
978 "Taker partially crossed, Flow fully crossed";
979 if (takerR.second.in == takerAmount.in &&
980 takerR.second.out == takerAmount.out)
981 crossType =
"Taker not crossed, Flow fully crossed";
988 "FillOrKill offer", ctx_, sbCancelTaker, psbCancelFlow, j_);
990 else if (takerR.second.in == takerAmount.in &&
991 flowR.second.in == takerAmount.in &&
992 takerR.second.out == takerAmount.out &&
993 flowR.second.out == takerAmount.out)
995 char const * crossType =
"Neither Taker nor Flow crossed";
998 else if (takerR.second.in == takerAmount.in &&
999 takerR.second.out == takerAmount.out)
1001 char const * crossType =
"Taker not crossed, Flow partially crossed";
1004 else if (flowR.second.in == takerAmount.in &&
1005 flowR.second.out == takerAmount.out)
1007 char const * crossType =
"Taker partially crossed, Flow not crossed";
1013 "Partial cross offer", ctx_, sbTaker, psbFlow, j_);
1016 if (c <= SBoxCmp::dustDiff && takerR.second != flowR.second)
1018 c = SBoxCmp::dustDiff;
1022 if (!
diffIsDust (takerR.second.in, flowR.second.in) ||
1023 (!
diffIsDust (takerR.second.out, flowR.second.out)))
1025 char const* outSame =
"";
1026 if (takerR.second.out == flowR.second.out)
1027 outSame =
" but outs same";
1033 <<
". Taker in: " << takerR.second.in.getText()
1034 <<
"; Taker out: " << takerR.second.out.getText()
1035 <<
"; Flow in: " << flowR.second.in.getText()
1036 <<
"; Flow out: " << flowR.second.out.getText()
1037 <<
". tx: " << ctx_.tx.getTransactionID();
1038 onlyDust = ss.
str();
1040 j_.
stream (s) <<
"FlowCross: Partial cross amounts different"
1044 j_.
error() <<
"FlowCross cmp result: " << to_string (c);
1051 psbCancelFlow.apply (sbCancel);
1056 sbCancelTaker.apply (sbCancel);
1070 CreateOffer::preCompute()
1072 cross_type_ = CrossType::IouToIou;
1073 bool const pays_xrp =
1075 bool const gets_xrp =
1077 if (pays_xrp && !gets_xrp)
1078 cross_type_ = CrossType::IouToXrp;
1079 else if (gets_xrp && !pays_xrp)
1080 cross_type_ = CrossType::XrpToIou;
1082 return Transactor::preCompute();
1092 bool const bPassive (uTxFlags &
tfPassive);
1095 bool const bSell (uTxFlags &
tfSell);
1105 auto const uSequence = ctx_.tx.getSequence ();
1110 auto uRate =
getRate (saTakerGets, saTakerPays);
1112 auto viewJ = ctx_.app.journal(
"View");
1119 auto const sleCancel = sb.
peek(
1120 keylet::offer(account_, *cancelSequence));
1127 JLOG(j_.
debug()) <<
"Create cancels order " << *cancelSequence;
1140 (ctx_.view().parentCloseTime() >= tp{d{*expiration}}))
1147 TER const ter {ctx_.view().rules().enabled(
1148 featureDepositPreauth) ?
TER {
tecEXPIRED} : TER {tesSUCCESS}};
1149 return{ ter,
true };
1152 bool const bOpenLedger = ctx_.view().open();
1153 bool crossed =
false;
1155 if (result == tesSUCCESS)
1158 auto const& uPaysIssuerID = saTakerPays.getIssuer ();
1159 auto const& uGetsIssuerID = saTakerGets.getIssuer ();
1162 if (!isXRP (uPaysIssuerID))
1165 sb.read(keylet::account(uPaysIssuerID));
1166 if (sle && sle->isFieldPresent (sfTickSize))
1168 (*sle)[sfTickSize]);
1170 if (!isXRP (uGetsIssuerID))
1173 sb.read(keylet::account(uGetsIssuerID));
1174 if (sle && sle->isFieldPresent (sfTickSize))
1176 (*sle)[sfTickSize]);
1178 if (uTickSize < Quality::maxTickSize)
1181 Quality{saTakerGets, saTakerPays}.round
1191 saTakerGets, rate, saTakerPays.issue());
1197 saTakerPays, rate, saTakerGets.issue());
1199 if (! saTakerGets || ! saTakerPays)
1201 JLOG (j_.
debug()) <<
1202 "Offer rounded to zero";
1203 return { result,
true };
1206 uRate =
getRate (saTakerGets, saTakerPays);
1210 Amounts
const takerAmount (saTakerGets, saTakerPays);
1215 Amounts place_offer;
1217 JLOG(j_.
debug()) <<
"Attempting cross: " <<
1218 to_string (takerAmount.in.issue ()) <<
" -> " <<
1221 if (
auto stream = j_.
trace())
1224 (bPassive ?
"passive " :
"") <<
1225 (bSell ?
"sell" :
"buy");
1226 stream <<
" in: " << format_amount (takerAmount.in);
1227 stream <<
" out: " << format_amount (takerAmount.out);
1230 std::tie(result, place_offer) = cross (sb, sbCancel, takerAmount);
1234 assert(result == tesSUCCESS ||
isTecClaim(result));
1236 if (
auto stream = j_.
trace())
1239 stream <<
" in: " << format_amount (place_offer.in);
1240 stream <<
" out: " << format_amount (place_offer.out);
1243 if (result == tecFAILED_PROCESSING && bOpenLedger)
1246 if (result != tesSUCCESS)
1249 return { result,
true };
1252 assert (saTakerGets.issue () == place_offer.in.issue ());
1253 assert (saTakerPays.issue () == place_offer.out.issue ());
1255 if (takerAmount != place_offer)
1260 if (place_offer.in < zero || place_offer.out < zero)
1262 JLOG(j_.
fatal()) <<
"Cross left offer negative!" <<
1263 " in: " << format_amount (place_offer.in) <<
1264 " out: " << format_amount (place_offer.out);
1268 if (place_offer.in == zero || place_offer.out == zero)
1270 JLOG(j_.
debug()) <<
"Offer fully crossed!";
1271 return { result,
true };
1277 saTakerPays = place_offer.out;
1278 saTakerGets = place_offer.in;
1281 assert (saTakerPays > zero && saTakerGets > zero);
1283 if (result != tesSUCCESS)
1286 return { result,
true };
1289 if (
auto stream = j_.
trace())
1291 stream <<
"Place" << (crossed ?
" remaining " :
" ") <<
"offer:";
1292 stream <<
" Pays: " << saTakerPays.getFullText ();
1293 stream <<
" Gets: " << saTakerGets.getFullText ();
1300 JLOG (j_.
trace()) <<
"Fill or Kill: offer killed";
1301 if (sb.rules().enabled (fix1578))
1308 if (bImmediateOrCancel)
1310 JLOG (j_.
trace()) <<
"Immediate or cancel: offer canceled";
1314 auto const sleCreator = sb.peek (keylet::account(account_));
1319 XRPAmount reserve = ctx_.view().fees().accountReserve(
1320 sleCreator->getFieldU32 (sfOwnerCount) + 1);
1322 if (mPriorBalance < reserve)
1330 if (result != tesSUCCESS)
1332 JLOG (j_.
debug()) <<
1336 return { result,
true };
1341 auto const offer_index =
getOfferIndex (account_, uSequence);
1344 auto const ownerNode =
dirAdd(sb, keylet::ownerDir (account_),
1345 offer_index,
false, describeOwnerDir (account_), viewJ);
1349 JLOG (j_.
debug()) <<
1350 "final result: failed to add offer to owner's directory";
1357 JLOG (j_.
trace()) <<
1358 "adding to book: " <<
to_string (saTakerPays.issue ()) <<
1359 " : " <<
to_string (saTakerGets.issue ());
1361 Book
const book { saTakerPays.issue(), saTakerGets.issue() };
1365 auto dir = keylet::quality (keylet::book (book), uRate);
1366 bool const bookExisted =
static_cast<bool>(sb.peek (dir));
1368 auto const bookNode =
dirAdd (sb, dir, offer_index,
true,
1371 sle->setFieldH160 (sfTakerPaysCurrency,
1372 saTakerPays.issue().currency);
1373 sle->setFieldH160 (sfTakerPaysIssuer,
1374 saTakerPays.issue().account);
1375 sle->setFieldH160 (sfTakerGetsCurrency,
1376 saTakerGets.issue().currency);
1377 sle->setFieldH160 (sfTakerGetsIssuer,
1378 saTakerGets.issue().account);
1379 sle->setFieldU64 (sfExchangeRate, uRate);
1384 JLOG (j_.
debug()) <<
1385 "final result: failed to add offer to book";
1389 auto sleOffer = std::make_shared<SLE>(ltOFFER, offer_index);
1390 sleOffer->setAccountID (sfAccount, account_);
1391 sleOffer->setFieldU32 (sfSequence, uSequence);
1392 sleOffer->setFieldH256 (sfBookDirectory, dir.key);
1393 sleOffer->setFieldAmount (sfTakerPays, saTakerPays);
1394 sleOffer->setFieldAmount (sfTakerGets, saTakerGets);
1395 sleOffer->setFieldU64 (sfOwnerNode, *ownerNode);
1396 sleOffer->setFieldU64 (sfBookNode, *bookNode);
1398 sleOffer->setFieldU32 (sfExpiration, *expiration);
1400 sleOffer->setFlag (lsfPassive);
1402 sleOffer->setFlag (lsfSell);
1403 sb.insert(sleOffer);
1406 ctx_.app.getOrderBookDB().addOrderBook(book);
1408 JLOG (j_.
debug()) <<
"final result: success";
1414 CreateOffer::doApply()
1423 Sandbox sbCancel (&ctx_.view());
1425 auto const result = applyGuts(sb, sbCancel);
1427 sb.
apply(ctx_.rawView());
1429 sbCancel.
apply(ctx_.rawView());
1430 return result.first;