rippled
Loading...
Searching...
No Matches
CreateOffer.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/ledger/OrderBookDB.h>
21#include <xrpld/app/misc/PermissionedDEXHelpers.h>
22#include <xrpld/app/paths/Flow.h>
23#include <xrpld/app/tx/detail/CreateOffer.h>
24#include <xrpld/ledger/PaymentSandbox.h>
25
26#include <xrpl/basics/base_uint.h>
27#include <xrpl/beast/utility/WrappedSink.h>
28#include <xrpl/protocol/Feature.h>
29#include <xrpl/protocol/Quality.h>
30#include <xrpl/protocol/STAmount.h>
31#include <xrpl/protocol/TER.h>
32#include <xrpl/protocol/st.h>
33
34namespace ripple {
35TxConsequences
37{
38 auto calculateMaxXRPSpend = [](STTx const& tx) -> XRPAmount {
39 auto const& amount{tx[sfTakerGets]};
40 return amount.native() ? amount.xrp() : beast::zero;
41 };
42
43 return TxConsequences{ctx.tx, calculateMaxXRPSpend(ctx.tx)};
44}
45
48{
49 if (ctx.tx.isFieldPresent(sfDomainID) &&
50 !ctx.rules.enabled(featurePermissionedDEX))
51 return temDISABLED;
52
53 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
54 return ret;
55
56 auto& tx = ctx.tx;
57 auto& j = ctx.j;
58
59 std::uint32_t const uTxFlags = tx.getFlags();
60
61 if (uTxFlags & tfOfferCreateMask)
62 {
63 JLOG(j.debug()) << "Malformed transaction: Invalid flags set.";
64 return temINVALID_FLAG;
65 }
66
67 if (!ctx.rules.enabled(featurePermissionedDEX) && tx.isFlag(tfHybrid))
68 return temINVALID_FLAG;
69
70 if (tx.isFlag(tfHybrid) && !tx.isFieldPresent(sfDomainID))
71 return temINVALID_FLAG;
72
73 bool const bImmediateOrCancel(uTxFlags & tfImmediateOrCancel);
74 bool const bFillOrKill(uTxFlags & tfFillOrKill);
75
76 if (bImmediateOrCancel && bFillOrKill)
77 {
78 JLOG(j.debug()) << "Malformed transaction: both IoC and FoK set.";
79 return temINVALID_FLAG;
80 }
81
82 bool const bHaveExpiration(tx.isFieldPresent(sfExpiration));
83
84 if (bHaveExpiration && (tx.getFieldU32(sfExpiration) == 0))
85 {
86 JLOG(j.debug()) << "Malformed offer: bad expiration";
87 return temBAD_EXPIRATION;
88 }
89
90 if (auto const cancelSequence = tx[~sfOfferSequence];
91 cancelSequence && *cancelSequence == 0)
92 {
93 JLOG(j.debug()) << "Malformed offer: bad cancel sequence";
94 return temBAD_SEQUENCE;
95 }
96
97 STAmount saTakerPays = tx[sfTakerPays];
98 STAmount saTakerGets = tx[sfTakerGets];
99
100 if (!isLegalNet(saTakerPays) || !isLegalNet(saTakerGets))
101 return temBAD_AMOUNT;
102
103 if (saTakerPays.native() && saTakerGets.native())
104 {
105 JLOG(j.debug()) << "Malformed offer: redundant (XRP for XRP)";
106 return temBAD_OFFER;
107 }
108 if (saTakerPays <= beast::zero || saTakerGets <= beast::zero)
109 {
110 JLOG(j.debug()) << "Malformed offer: bad amount";
111 return temBAD_OFFER;
112 }
113
114 auto const& uPaysIssuerID = saTakerPays.getIssuer();
115 auto const& uPaysCurrency = saTakerPays.getCurrency();
116
117 auto const& uGetsIssuerID = saTakerGets.getIssuer();
118 auto const& uGetsCurrency = saTakerGets.getCurrency();
119
120 if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID)
121 {
122 JLOG(j.debug()) << "Malformed offer: redundant (IOU for IOU)";
123 return temREDUNDANT;
124 }
125 // We don't allow a non-native currency to use the currency code XRP.
126 if (badCurrency() == uPaysCurrency || badCurrency() == uGetsCurrency)
127 {
128 JLOG(j.debug()) << "Malformed offer: bad currency";
129 return temBAD_CURRENCY;
130 }
131
132 if (saTakerPays.native() != !uPaysIssuerID ||
133 saTakerGets.native() != !uGetsIssuerID)
134 {
135 JLOG(j.debug()) << "Malformed offer: bad issuer";
136 return temBAD_ISSUER;
137 }
138
139 return preflight2(ctx);
140}
141
142TER
144{
145 auto const id = ctx.tx[sfAccount];
146
147 auto saTakerPays = ctx.tx[sfTakerPays];
148 auto saTakerGets = ctx.tx[sfTakerGets];
149
150 auto const& uPaysIssuerID = saTakerPays.getIssuer();
151 auto const& uPaysCurrency = saTakerPays.getCurrency();
152
153 auto const& uGetsIssuerID = saTakerGets.getIssuer();
154
155 auto const cancelSequence = ctx.tx[~sfOfferSequence];
156
157 auto const sleCreator = ctx.view.read(keylet::account(id));
158 if (!sleCreator)
159 return terNO_ACCOUNT;
160
161 std::uint32_t const uAccountSequence = sleCreator->getFieldU32(sfSequence);
162
163 auto viewJ = ctx.app.journal("View");
164
165 if (isGlobalFrozen(ctx.view, uPaysIssuerID) ||
166 isGlobalFrozen(ctx.view, uGetsIssuerID))
167 {
168 JLOG(ctx.j.debug()) << "Offer involves frozen asset";
169 return tecFROZEN;
170 }
171
172 if (accountFunds(ctx.view, id, saTakerGets, fhZERO_IF_FROZEN, viewJ) <=
173 beast::zero)
174 {
175 JLOG(ctx.j.debug())
176 << "delay: Offers must be at least partially funded.";
177 return tecUNFUNDED_OFFER;
178 }
179
180 // This can probably be simplified to make sure that you cancel sequences
181 // before the transaction sequence number.
182 if (cancelSequence && (uAccountSequence <= *cancelSequence))
183 {
184 JLOG(ctx.j.debug()) << "uAccountSequenceNext=" << uAccountSequence
185 << " uOfferSequence=" << *cancelSequence;
186 return temBAD_SEQUENCE;
187 }
188
189 if (hasExpired(ctx.view, ctx.tx[~sfExpiration]))
190 {
191 // Note that this will get checked again in applyGuts, but it saves
192 // us a call to checkAcceptAsset and possible false negative.
193 //
194 // The return code change is attached to featureDepositPreauth as a
195 // convenience, as the change is not big enough to deserve its own
196 // amendment.
197 return ctx.view.rules().enabled(featureDepositPreauth)
198 ? TER{tecEXPIRED}
199 : TER{tesSUCCESS};
200 }
201
202 // Make sure that we are authorized to hold what the taker will pay us.
203 if (!saTakerPays.native())
204 {
205 auto result = checkAcceptAsset(
206 ctx.view,
207 ctx.flags,
208 id,
209 ctx.j,
210 Issue(uPaysCurrency, uPaysIssuerID));
211 if (result != tesSUCCESS)
212 return result;
213 }
214
215 // if domain is specified, make sure that domain exists and the offer create
216 // is part of the domain
217 if (ctx.tx.isFieldPresent(sfDomainID))
218 {
220 ctx.view, id, ctx.tx[sfDomainID]))
221 return tecNO_PERMISSION;
222 }
223
224 return tesSUCCESS;
225}
226
227TER
229 ReadView const& view,
230 ApplyFlags const flags,
231 AccountID const id,
232 beast::Journal const j,
233 Issue const& issue)
234{
235 // Only valid for custom currencies
236 XRPL_ASSERT(
237 !isXRP(issue.currency),
238 "ripple::CreateOffer::checkAcceptAsset : input is not XRP");
239
240 auto const issuerAccount = view.read(keylet::account(issue.account));
241
242 if (!issuerAccount)
243 {
244 JLOG(j.debug())
245 << "delay: can't receive IOUs from non-existent issuer: "
246 << to_string(issue.account);
247
248 return (flags & tapRETRY) ? TER{terNO_ACCOUNT} : TER{tecNO_ISSUER};
249 }
250
251 // This code is attached to the DepositPreauth amendment as a matter of
252 // convenience. The change is not significant enough to deserve its
253 // own amendment.
254 if (view.rules().enabled(featureDepositPreauth) && (issue.account == id))
255 // An account can always accept its own issuance.
256 return tesSUCCESS;
257
258 if ((*issuerAccount)[sfFlags] & lsfRequireAuth)
259 {
260 auto const trustLine =
261 view.read(keylet::line(id, issue.account, issue.currency));
262
263 if (!trustLine)
264 {
265 return (flags & tapRETRY) ? TER{terNO_LINE} : TER{tecNO_LINE};
266 }
267
268 // Entries have a canonical representation, determined by a
269 // lexicographical "greater than" comparison employing strict weak
270 // ordering. Determine which entry we need to access.
271 bool const canonical_gt(id > issue.account);
272
273 bool const is_authorized(
274 (*trustLine)[sfFlags] & (canonical_gt ? lsfLowAuth : lsfHighAuth));
275
276 if (!is_authorized)
277 {
278 JLOG(j.debug())
279 << "delay: can't receive IOUs from issuer without auth.";
280
281 return (flags & tapRETRY) ? TER{terNO_AUTH} : TER{tecNO_AUTH};
282 }
283 }
284
285 // An account can not create a trustline to itself, so no line can exist
286 // to be frozen. Additionally, an issuer can always accept its own
287 // issuance.
288 if (issue.account == id)
289 {
290 return tesSUCCESS;
291 }
292
293 auto const trustLine =
294 view.read(keylet::line(id, issue.account, issue.currency));
295
296 if (!trustLine)
297 {
298 return tesSUCCESS;
299 }
300
301 // There's no difference which side enacted deep freeze, accepting
302 // tokens shouldn't be possible.
303 bool const deepFrozen =
304 (*trustLine)[sfFlags] & (lsfLowDeepFreeze | lsfHighDeepFreeze);
305
306 if (deepFrozen)
307 {
308 return tecFROZEN;
309 }
310
311 return tesSUCCESS;
312}
313
314bool
316{
317 if (offer.fully_consumed())
318 return true;
319 auto const amount = accountFunds(
320 view,
321 offer.owner(),
322 offer.amount().out,
324 ctx_.app.journal("View"));
325 return (amount <= beast::zero);
326}
327
330 bool have_direct,
331 OfferStream const& direct,
332 bool have_bridge,
333 OfferStream const& leg1,
334 OfferStream const& leg2)
335{
336 // If we don't have any viable path, why are we here?!
337 XRPL_ASSERT(
338 have_direct || have_bridge,
339 "ripple::CreateOffer::select_path : valid inputs");
340
341 // If there's no bridged path, the direct is the best by default.
342 if (!have_bridge)
343 return std::make_pair(true, direct.tip().quality());
344
345 Quality const bridged_quality(
346 composed_quality(leg1.tip().quality(), leg2.tip().quality()));
347
348 if (have_direct)
349 {
350 // We compare the quality of the composed quality of the bridged
351 // offers and compare it against the direct offer to pick the best.
352 Quality const direct_quality(direct.tip().quality());
353
354 if (bridged_quality < direct_quality)
355 return std::make_pair(true, direct_quality);
356 }
357
358 // Either there was no direct offer, or it didn't have a better quality
359 // than the bridge.
360 return std::make_pair(false, bridged_quality);
361}
362
363bool
365{
366 auto const crossings =
367 taker.get_direct_crossings() + (2 * taker.get_bridge_crossings());
368
369 // The crossing limit is part of the Ripple protocol and
370 // changing it is a transaction-processing change.
371 return crossings >= 850;
372}
373
376 Taker& taker,
377 ApplyView& view,
378 ApplyView& view_cancel,
379 NetClock::time_point const when)
380{
381 auto const& takerAmount = taker.original_offer();
382
383 XRPL_ASSERT(
384 !isXRP(takerAmount.in) && !isXRP(takerAmount.out),
385 "ripple::CreateOffer::bridged_cross : neither is XRP");
386
387 if (isXRP(takerAmount.in) || isXRP(takerAmount.out))
388 Throw<std::logic_error>("Bridging with XRP and an endpoint.");
389
390 OfferStream offers_direct(
391 view,
392 view_cancel,
393 Book(taker.issue_in(), taker.issue_out(), std::nullopt),
394 when,
396 j_);
397
398 OfferStream offers_leg1(
399 view,
400 view_cancel,
401 Book(taker.issue_in(), xrpIssue(), std::nullopt),
402 when,
404 j_);
405
406 OfferStream offers_leg2(
407 view,
408 view_cancel,
409 Book(xrpIssue(), taker.issue_out(), std::nullopt),
410 when,
412 j_);
413
414 TER cross_result = tesSUCCESS;
415
416 // Note the subtle distinction here: self-offers encountered in the
417 // bridge are taken, but self-offers encountered in the direct book
418 // are not.
419 bool have_bridge = offers_leg1.step() && offers_leg2.step();
420 bool have_direct = step_account(offers_direct, taker);
421 int count = 0;
422
423 auto viewJ = ctx_.app.journal("View");
424
425 // Modifying the order or logic of the operations in the loop will cause
426 // a protocol breaking change.
427 while (have_direct || have_bridge)
428 {
429 bool leg1_consumed = false;
430 bool leg2_consumed = false;
431 bool direct_consumed = false;
432
433 auto const [use_direct, quality] = select_path(
434 have_direct, offers_direct, have_bridge, offers_leg1, offers_leg2);
435
436 // We are always looking at the best quality; we are done with
437 // crossing as soon as we cross the quality boundary.
438 if (taker.reject(quality))
439 break;
440
441 count++;
442
443 if (use_direct)
444 {
445 if (auto stream = j_.debug())
446 {
447 stream << count << " Direct:";
448 stream << " offer: " << offers_direct.tip();
449 stream << " in: " << offers_direct.tip().amount().in;
450 stream << " out: " << offers_direct.tip().amount().out;
451 stream << " owner: " << offers_direct.tip().owner();
452 stream << " funds: "
453 << accountFunds(
454 view,
455 offers_direct.tip().owner(),
456 offers_direct.tip().amount().out,
458 viewJ);
459 }
460
461 cross_result = taker.cross(offers_direct.tip());
462
463 JLOG(j_.debug()) << "Direct Result: " << transToken(cross_result);
464
465 if (dry_offer(view, offers_direct.tip()))
466 {
467 direct_consumed = true;
468 have_direct = step_account(offers_direct, taker);
469 }
470 }
471 else
472 {
473 if (auto stream = j_.debug())
474 {
475 auto const owner1_funds_before = accountFunds(
476 view,
477 offers_leg1.tip().owner(),
478 offers_leg1.tip().amount().out,
480 viewJ);
481
482 auto const owner2_funds_before = accountFunds(
483 view,
484 offers_leg2.tip().owner(),
485 offers_leg2.tip().amount().out,
487 viewJ);
488
489 stream << count << " Bridge:";
490 stream << " offer1: " << offers_leg1.tip();
491 stream << " in: " << offers_leg1.tip().amount().in;
492 stream << " out: " << offers_leg1.tip().amount().out;
493 stream << " owner: " << offers_leg1.tip().owner();
494 stream << " funds: " << owner1_funds_before;
495 stream << " offer2: " << offers_leg2.tip();
496 stream << " in: " << offers_leg2.tip().amount().in;
497 stream << " out: " << offers_leg2.tip().amount().out;
498 stream << " owner: " << offers_leg2.tip().owner();
499 stream << " funds: " << owner2_funds_before;
500 }
501
502 cross_result = taker.cross(offers_leg1.tip(), offers_leg2.tip());
503
504 JLOG(j_.debug()) << "Bridge Result: " << transToken(cross_result);
505
506 if (view.rules().enabled(fixTakerDryOfferRemoval))
507 {
508 // have_bridge can be true the next time 'round only if
509 // neither of the OfferStreams are dry.
510 leg1_consumed = dry_offer(view, offers_leg1.tip());
511 if (leg1_consumed)
512 have_bridge &= offers_leg1.step();
513
514 leg2_consumed = dry_offer(view, offers_leg2.tip());
515 if (leg2_consumed)
516 have_bridge &= offers_leg2.step();
517 }
518 else
519 {
520 // This old behavior may leave an empty offer in the book for
521 // the second leg.
522 if (dry_offer(view, offers_leg1.tip()))
523 {
524 leg1_consumed = true;
525 have_bridge = (have_bridge && offers_leg1.step());
526 }
527 if (dry_offer(view, offers_leg2.tip()))
528 {
529 leg2_consumed = true;
530 have_bridge = (have_bridge && offers_leg2.step());
531 }
532 }
533 }
534
535 if (cross_result != tesSUCCESS)
536 {
537 cross_result = tecFAILED_PROCESSING;
538 break;
539 }
540
541 if (taker.done())
542 {
543 JLOG(j_.debug()) << "The taker reports he's done during crossing!";
544 break;
545 }
546
547 if (reachedOfferCrossingLimit(taker))
548 {
549 JLOG(j_.debug()) << "The offer crossing limit has been exceeded!";
550 break;
551 }
552
553 // Postcondition: If we aren't done, then we *must* have consumed at
554 // least one offer fully.
555 XRPL_ASSERT(
556 direct_consumed || leg1_consumed || leg2_consumed,
557 "ripple::CreateOffer::bridged_cross : consumed an offer");
558
559 if (!direct_consumed && !leg1_consumed && !leg2_consumed)
560 Throw<std::logic_error>(
561 "bridged crossing: nothing was fully consumed.");
562 }
563
564 return std::make_pair(cross_result, taker.remaining_offer());
565}
566
569 Taker& taker,
570 ApplyView& view,
571 ApplyView& view_cancel,
572 NetClock::time_point const when)
573{
574 OfferStream offers(
575 view,
576 view_cancel,
577 Book(taker.issue_in(), taker.issue_out(), std::nullopt),
578 when,
580 j_);
581
582 TER cross_result(tesSUCCESS);
583 int count = 0;
584
585 bool have_offer = step_account(offers, taker);
586
587 // Modifying the order or logic of the operations in the loop will cause
588 // a protocol breaking change.
589 while (have_offer)
590 {
591 bool direct_consumed = false;
592 auto& offer(offers.tip());
593
594 // We are done with crossing as soon as we cross the quality boundary
595 if (taker.reject(offer.quality()))
596 break;
597
598 count++;
599
600 if (auto stream = j_.debug())
601 {
602 stream << count << " Direct:";
603 stream << " offer: " << offer;
604 stream << " in: " << offer.amount().in;
605 stream << " out: " << offer.amount().out;
606 stream << "quality: " << offer.quality();
607 stream << " owner: " << offer.owner();
608 stream << " funds: "
609 << accountFunds(
610 view,
611 offer.owner(),
612 offer.amount().out,
614 ctx_.app.journal("View"));
615 }
616
617 cross_result = taker.cross(offer);
618
619 JLOG(j_.debug()) << "Direct Result: " << transToken(cross_result);
620
621 if (dry_offer(view, offer))
622 {
623 direct_consumed = true;
624 have_offer = step_account(offers, taker);
625 }
626
627 if (cross_result != tesSUCCESS)
628 {
629 cross_result = tecFAILED_PROCESSING;
630 break;
631 }
632
633 if (taker.done())
634 {
635 JLOG(j_.debug()) << "The taker reports he's done during crossing!";
636 break;
637 }
638
639 if (reachedOfferCrossingLimit(taker))
640 {
641 JLOG(j_.debug()) << "The offer crossing limit has been exceeded!";
642 break;
643 }
644
645 // Postcondition: If we aren't done, then we *must* have consumed the
646 // offer on the books fully!
647 XRPL_ASSERT(
648 direct_consumed,
649 "ripple::CreateOffer::direct_cross : consumed an offer");
650
651 if (!direct_consumed)
652 Throw<std::logic_error>(
653 "direct crossing: nothing was fully consumed.");
654 }
655
656 return std::make_pair(cross_result, taker.remaining_offer());
657}
658
659// Step through the stream for as long as possible, skipping any offers
660// that are from the taker or which cross the taker's threshold.
661// Return false if the is no offer in the book, true otherwise.
662bool
664{
665 while (stream.step())
666 {
667 auto const& offer = stream.tip();
668
669 // This offer at the tip crosses the taker's threshold. We're done.
670 if (taker.reject(offer.quality()))
671 return true;
672
673 // This offer at the tip is not from the taker. We're done.
674 if (offer.owner() != taker.account())
675 return true;
676 }
677
678 // We ran out of offers. Can't advance.
679 return false;
680}
681
684 PaymentSandbox& psb,
685 PaymentSandbox& psbCancel,
686 Amounts const& takerAmount,
687 std::optional<uint256> const& domainID)
688{
689 try
690 {
691 // If the taker is unfunded before we begin crossing there's nothing
692 // to do - just return an error.
693 //
694 // We check this in preclaim, but when selling XRP charged fees can
695 // cause a user's available balance to go to 0 (by causing it to dip
696 // below the reserve) so we check this case again.
697 STAmount const inStartBalance =
698 accountFunds(psb, account_, takerAmount.in, fhZERO_IF_FROZEN, j_);
699 if (inStartBalance <= beast::zero)
700 {
701 // The account balance can't cover even part of the offer.
702 JLOG(j_.debug()) << "Not crossing: taker is unfunded.";
703 return {tecUNFUNDED_OFFER, takerAmount};
704 }
705
706 // If the gateway has a transfer rate, accommodate that. The
707 // gateway takes its cut without any special consent from the
708 // offer taker. Set sendMax to allow for the gateway's cut.
709 Rate gatewayXferRate{QUALITY_ONE};
710 STAmount sendMax = takerAmount.in;
711 if (!sendMax.native() && (account_ != sendMax.getIssuer()))
712 {
713 gatewayXferRate = transferRate(psb, sendMax.getIssuer());
714 if (gatewayXferRate.value != QUALITY_ONE)
715 {
716 sendMax = multiplyRound(
717 takerAmount.in,
718 gatewayXferRate,
719 takerAmount.in.issue(),
720 true);
721 }
722 }
723
724 // Payment flow code compares quality after the transfer rate is
725 // included. Since transfer rate is incorporated compute threshold.
726 Quality threshold{takerAmount.out, sendMax};
727
728 // If we're creating a passive offer adjust the threshold so we only
729 // cross offers that have a better quality than this one.
730 std::uint32_t const txFlags = ctx_.tx.getFlags();
731 if (txFlags & tfPassive)
732 ++threshold;
733
734 // Don't send more than our balance.
735 if (sendMax > inStartBalance)
736 sendMax = inStartBalance;
737
738 // Always invoke flow() with the default path. However if neither
739 // of the takerAmount currencies are XRP then we cross through an
740 // additional path with XRP as the intermediate between two books.
741 // This second path we have to build ourselves.
742 STPathSet paths;
743 if (!takerAmount.in.native() && !takerAmount.out.native())
744 {
745 STPath path;
746 path.emplace_back(std::nullopt, xrpCurrency(), std::nullopt);
747 paths.emplace_back(std::move(path));
748 }
749 // Special handling for the tfSell flag.
750 STAmount deliver = takerAmount.out;
751 OfferCrossing offerCrossing = OfferCrossing::yes;
752 if (txFlags & tfSell)
753 {
754 offerCrossing = OfferCrossing::sell;
755 // We are selling, so we will accept *more* than the offer
756 // specified. Since we don't know how much they might offer,
757 // we allow delivery of the largest possible amount.
758 if (deliver.native())
760 else
761 // We can't use the maximum possible currency here because
762 // there might be a gateway transfer rate to account for.
763 // Since the transfer rate cannot exceed 200%, we use 1/2
764 // maxValue for our limit.
765 deliver = STAmount{
766 takerAmount.out.issue(),
769 }
770
771 // Call the payment engine's flow() to do the actual work.
772 auto const result = flow(
773 psb,
774 deliver,
775 account_,
776 account_,
777 paths,
778 true, // default path
779 !(txFlags & tfFillOrKill), // partial payment
780 true, // owner pays transfer fee
781 offerCrossing,
782 threshold,
783 sendMax,
784 domainID,
785 j_);
786
787 // If stale offers were found remove them.
788 for (auto const& toRemove : result.removableOffers)
789 {
790 if (auto otr = psb.peek(keylet::offer(toRemove)))
791 offerDelete(psb, otr, j_);
792 if (auto otr = psbCancel.peek(keylet::offer(toRemove)))
793 offerDelete(psbCancel, otr, j_);
794 }
795
796 // Determine the size of the final offer after crossing.
797 auto afterCross = takerAmount; // If !tesSUCCESS offer unchanged
798 if (isTesSuccess(result.result()))
799 {
800 STAmount const takerInBalance = accountFunds(
801 psb, account_, takerAmount.in, fhZERO_IF_FROZEN, j_);
802
803 if (takerInBalance <= beast::zero)
804 {
805 // If offer crossing exhausted the account's funds don't
806 // create the offer.
807 afterCross.in.clear();
808 afterCross.out.clear();
809 }
810 else
811 {
812 STAmount const rate{
813 Quality{takerAmount.out, takerAmount.in}.rate()};
814
815 if (txFlags & tfSell)
816 {
817 // If selling then scale the new out amount based on how
818 // much we sold during crossing. This preserves the offer
819 // Quality,
820
821 // Reduce the offer that is placed by the crossed amount.
822 // Note that we must ignore the portion of the
823 // actualAmountIn that may have been consumed by a
824 // gateway's transfer rate.
825 STAmount nonGatewayAmountIn = result.actualAmountIn;
826 if (gatewayXferRate.value != QUALITY_ONE)
827 nonGatewayAmountIn = divideRound(
828 result.actualAmountIn,
829 gatewayXferRate,
830 takerAmount.in.issue(),
831 true);
832
833 afterCross.in -= nonGatewayAmountIn;
834
835 // It's possible that the divRound will cause our subtract
836 // to go slightly negative. So limit afterCross.in to zero.
837 if (afterCross.in < beast::zero)
838 // We should verify that the difference *is* small, but
839 // what is a good threshold to check?
840 afterCross.in.clear();
841
842 afterCross.out = [&]() {
843 // Careful analysis showed that rounding up this
844 // divRound result could lead to placing a reduced
845 // offer in the ledger that blocks order books. So
846 // the fixReducedOffersV1 amendment changes the
847 // behavior to round down instead.
848 if (psb.rules().enabled(fixReducedOffersV1))
849 return divRoundStrict(
850 afterCross.in,
851 rate,
852 takerAmount.out.issue(),
853 false);
854
855 return divRound(
856 afterCross.in, rate, takerAmount.out.issue(), true);
857 }();
858 }
859 else
860 {
861 // If not selling, we scale the input based on the
862 // remaining output. This too preserves the offer
863 // Quality.
864 afterCross.out -= result.actualAmountOut;
865 XRPL_ASSERT(
866 afterCross.out >= beast::zero,
867 "ripple::CreateOffer::flowCross : minimum offer");
868 if (afterCross.out < beast::zero)
869 afterCross.out.clear();
870 afterCross.in = mulRound(
871 afterCross.out, rate, takerAmount.in.issue(), true);
872 }
873 }
874 }
875
876 // Return how much of the offer is left.
877 return {tesSUCCESS, afterCross};
878 }
879 catch (std::exception const& e)
880 {
881 JLOG(j_.error()) << "Exception during offer crossing: " << e.what();
882 }
883 return {tecINTERNAL, takerAmount};
884}
885
888 Sandbox& sb,
889 Sandbox& sbCancel,
890 Amounts const& takerAmount,
891 std::optional<uint256> const& domainID)
892{
893 PaymentSandbox psbFlow{&sb};
894 PaymentSandbox psbCancelFlow{&sbCancel};
895 auto const ret = flowCross(psbFlow, psbCancelFlow, takerAmount, domainID);
896 psbFlow.apply(sb);
897 psbCancelFlow.apply(sbCancel);
898 return ret;
899}
900
903{
904 std::string txt = amount.getText();
905 txt += "/";
906 txt += to_string(amount.issue().currency);
907 return txt;
908}
909
910void
912{
914 bool const pays_xrp = ctx_.tx.getFieldAmount(sfTakerPays).native();
915 bool const gets_xrp = ctx_.tx.getFieldAmount(sfTakerGets).native();
916 if (pays_xrp && !gets_xrp)
918 else if (gets_xrp && !pays_xrp)
920
921 return Transactor::preCompute();
922}
923
924TER
926 Sandbox& sb,
928 Keylet const& offerKey,
929 STAmount const& saTakerPays,
930 STAmount const& saTakerGets,
931 std::function<void(SLE::ref, std::optional<uint256>)> const& setDir)
932{
933 if (!sleOffer->isFieldPresent(sfDomainID))
934 return tecINTERNAL; // LCOV_EXCL_LINE
935
936 // set hybrid flag
937 sleOffer->setFlag(lsfHybrid);
938
939 // if offer is hybrid, need to also place into open offer dir
940 Book const book{saTakerPays.issue(), saTakerGets.issue(), std::nullopt};
941
942 auto dir =
943 keylet::quality(keylet::book(book), getRate(saTakerGets, saTakerPays));
944 bool const bookExists = sb.exists(dir);
945
946 auto const bookNode = sb.dirAppend(dir, offerKey, [&](SLE::ref sle) {
947 // don't set domainID on the directory object since this directory is
948 // for open book
949 setDir(sle, std::nullopt);
950 });
951
952 if (!bookNode)
953 {
954 JLOG(j_.debug())
955 << "final result: failed to add hybrid offer to open book";
956 return tecDIR_FULL; // LCOV_EXCL_LINE
957 }
958
959 STArray bookArr(sfAdditionalBooks, 1);
960 auto bookInfo = STObject::makeInnerObject(sfBook);
961 bookInfo.setFieldH256(sfBookDirectory, dir.key);
962 bookInfo.setFieldU64(sfBookNode, *bookNode);
963 bookArr.push_back(std::move(bookInfo));
964
965 if (!bookExists)
967
968 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
969 return tesSUCCESS;
970}
971
974{
975 using beast::zero;
976
977 std::uint32_t const uTxFlags = ctx_.tx.getFlags();
978
979 bool const bPassive(uTxFlags & tfPassive);
980 bool const bImmediateOrCancel(uTxFlags & tfImmediateOrCancel);
981 bool const bFillOrKill(uTxFlags & tfFillOrKill);
982 bool const bSell(uTxFlags & tfSell);
983 bool const bHybrid(uTxFlags & tfHybrid);
984
985 auto saTakerPays = ctx_.tx[sfTakerPays];
986 auto saTakerGets = ctx_.tx[sfTakerGets];
987 auto const domainID = ctx_.tx[~sfDomainID];
988
989 auto const cancelSequence = ctx_.tx[~sfOfferSequence];
990
991 // Note that we we use the value from the sequence or ticket as the
992 // offer sequence. For more explanation see comments in SeqProxy.h.
993 auto const offerSequence = ctx_.tx.getSeqValue();
994
995 // This is the original rate of the offer, and is the rate at which
996 // it will be placed, even if crossing offers change the amounts that
997 // end up on the books.
998 auto uRate = getRate(saTakerGets, saTakerPays);
999
1000 auto viewJ = ctx_.app.journal("View");
1001
1002 TER result = tesSUCCESS;
1003
1004 // Process a cancellation request that's passed along with an offer.
1005 if (cancelSequence)
1006 {
1007 auto const sleCancel =
1008 sb.peek(keylet::offer(account_, *cancelSequence));
1009
1010 // It's not an error to not find the offer to cancel: it might have
1011 // been consumed or removed. If it is found, however, it's an error
1012 // to fail to delete it.
1013 if (sleCancel)
1014 {
1015 JLOG(j_.debug()) << "Create cancels order " << *cancelSequence;
1016 result = offerDelete(sb, sleCancel, viewJ);
1017 }
1018 }
1019
1020 auto const expiration = ctx_.tx[~sfExpiration];
1021
1022 if (hasExpired(sb, expiration))
1023 {
1024 // If the offer has expired, the transaction has successfully
1025 // done nothing, so short circuit from here.
1026 //
1027 // The return code change is attached to featureDepositPreauth as a
1028 // convenience. The change is not big enough to deserve a fix code.
1029 TER const ter{
1030 sb.rules().enabled(featureDepositPreauth) ? TER{tecEXPIRED}
1031 : TER{tesSUCCESS}};
1032 return {ter, true};
1033 }
1034
1035 bool const bOpenLedger = sb.open();
1036 bool crossed = false;
1037
1038 if (result == tesSUCCESS)
1039 {
1040 // If a tick size applies, round the offer to the tick size
1041 auto const& uPaysIssuerID = saTakerPays.getIssuer();
1042 auto const& uGetsIssuerID = saTakerGets.getIssuer();
1043
1044 std::uint8_t uTickSize = Quality::maxTickSize;
1045 if (!isXRP(uPaysIssuerID))
1046 {
1047 auto const sle = sb.read(keylet::account(uPaysIssuerID));
1048 if (sle && sle->isFieldPresent(sfTickSize))
1049 uTickSize = std::min(uTickSize, (*sle)[sfTickSize]);
1050 }
1051 if (!isXRP(uGetsIssuerID))
1052 {
1053 auto const sle = sb.read(keylet::account(uGetsIssuerID));
1054 if (sle && sle->isFieldPresent(sfTickSize))
1055 uTickSize = std::min(uTickSize, (*sle)[sfTickSize]);
1056 }
1057 if (uTickSize < Quality::maxTickSize)
1058 {
1059 auto const rate =
1060 Quality{saTakerGets, saTakerPays}.round(uTickSize).rate();
1061
1062 // We round the side that's not exact,
1063 // just as if the offer happened to execute
1064 // at a slightly better (for the placer) rate
1065 if (bSell)
1066 {
1067 // this is a sell, round taker pays
1068 saTakerPays = multiply(saTakerGets, rate, saTakerPays.issue());
1069 }
1070 else
1071 {
1072 // this is a buy, round taker gets
1073 saTakerGets = divide(saTakerPays, rate, saTakerGets.issue());
1074 }
1075 if (!saTakerGets || !saTakerPays)
1076 {
1077 JLOG(j_.debug()) << "Offer rounded to zero";
1078 return {result, true};
1079 }
1080
1081 uRate = getRate(saTakerGets, saTakerPays);
1082 }
1083
1084 // We reverse pays and gets because during crossing we are taking.
1085 Amounts const takerAmount(saTakerGets, saTakerPays);
1086
1087 // The amount of the offer that is unfilled after crossing has been
1088 // performed. It may be equal to the original amount (didn't cross),
1089 // empty (fully crossed), or something in-between.
1090 Amounts place_offer;
1091
1092 JLOG(j_.debug()) << "Attempting cross: "
1093 << to_string(takerAmount.in.issue()) << " -> "
1094 << to_string(takerAmount.out.issue());
1095
1096 if (auto stream = j_.trace())
1097 {
1098 stream << " mode: " << (bPassive ? "passive " : "")
1099 << (bSell ? "sell" : "buy");
1100 stream << " in: " << format_amount(takerAmount.in);
1101 stream << " out: " << format_amount(takerAmount.out);
1102 }
1103
1104 std::tie(result, place_offer) =
1105 cross(sb, sbCancel, takerAmount, domainID);
1106
1107 // We expect the implementation of cross to succeed
1108 // or give a tec.
1109 XRPL_ASSERT(
1110 result == tesSUCCESS || isTecClaim(result),
1111 "ripple::CreateOffer::applyGuts : result is tesSUCCESS or "
1112 "tecCLAIM");
1113
1114 if (auto stream = j_.trace())
1115 {
1116 stream << "Cross result: " << transToken(result);
1117 stream << " in: " << format_amount(place_offer.in);
1118 stream << " out: " << format_amount(place_offer.out);
1119 }
1120
1121 if (result == tecFAILED_PROCESSING && bOpenLedger)
1122 result = telFAILED_PROCESSING;
1123
1124 if (result != tesSUCCESS)
1125 {
1126 JLOG(j_.debug()) << "final result: " << transToken(result);
1127 return {result, true};
1128 }
1129
1130 XRPL_ASSERT(
1131 saTakerGets.issue() == place_offer.in.issue(),
1132 "ripple::CreateOffer::applyGuts : taker gets issue match");
1133 XRPL_ASSERT(
1134 saTakerPays.issue() == place_offer.out.issue(),
1135 "ripple::CreateOffer::applyGuts : taker pays issue match");
1136
1137 if (takerAmount != place_offer)
1138 crossed = true;
1139
1140 // The offer that we need to place after offer crossing should
1141 // never be negative. If it is, something went very very wrong.
1142 if (place_offer.in < zero || place_offer.out < zero)
1143 {
1144 JLOG(j_.fatal()) << "Cross left offer negative!"
1145 << " in: " << format_amount(place_offer.in)
1146 << " out: " << format_amount(place_offer.out);
1147 return {tefINTERNAL, true};
1148 }
1149
1150 if (place_offer.in == zero || place_offer.out == zero)
1151 {
1152 JLOG(j_.debug()) << "Offer fully crossed!";
1153 return {result, true};
1154 }
1155
1156 // We now need to adjust the offer to reflect the amount left after
1157 // crossing. We reverse in and out here, since during crossing we
1158 // were the taker.
1159 saTakerPays = place_offer.out;
1160 saTakerGets = place_offer.in;
1161 }
1162
1163 XRPL_ASSERT(
1164 saTakerPays > zero && saTakerGets > zero,
1165 "ripple::CreateOffer::applyGuts : taker pays and gets positive");
1166
1167 if (result != tesSUCCESS)
1168 {
1169 JLOG(j_.debug()) << "final result: " << transToken(result);
1170 return {result, true};
1171 }
1172
1173 if (auto stream = j_.trace())
1174 {
1175 stream << "Place" << (crossed ? " remaining " : " ") << "offer:";
1176 stream << " Pays: " << saTakerPays.getFullText();
1177 stream << " Gets: " << saTakerGets.getFullText();
1178 }
1179
1180 // For 'fill or kill' offers, failure to fully cross means that the
1181 // entire operation should be aborted, with only fees paid.
1182 if (bFillOrKill)
1183 {
1184 JLOG(j_.trace()) << "Fill or Kill: offer killed";
1185 if (sb.rules().enabled(fix1578))
1186 return {tecKILLED, false};
1187 return {tesSUCCESS, false};
1188 }
1189
1190 // For 'immediate or cancel' offers, the amount remaining doesn't get
1191 // placed - it gets canceled and the operation succeeds.
1192 if (bImmediateOrCancel)
1193 {
1194 JLOG(j_.trace()) << "Immediate or cancel: offer canceled";
1195 if (!crossed && sb.rules().enabled(featureImmediateOfferKilled))
1196 // If the ImmediateOfferKilled amendment is enabled, any
1197 // ImmediateOrCancel offer that transfers absolutely no funds
1198 // returns tecKILLED rather than tesSUCCESS. Motivation for the
1199 // change is here: https://github.com/ripple/rippled/issues/4115
1200 return {tecKILLED, false};
1201 return {tesSUCCESS, true};
1202 }
1203
1204 auto const sleCreator = sb.peek(keylet::account(account_));
1205 if (!sleCreator)
1206 return {tefINTERNAL, false};
1207
1208 {
1209 XRPAmount reserve =
1210 sb.fees().accountReserve(sleCreator->getFieldU32(sfOwnerCount) + 1);
1211
1212 if (mPriorBalance < reserve)
1213 {
1214 // If we are here, the signing account had an insufficient reserve
1215 // *prior* to our processing. If something actually crossed, then
1216 // we allow this; otherwise, we just claim a fee.
1217 if (!crossed)
1218 result = tecINSUF_RESERVE_OFFER;
1219
1220 if (result != tesSUCCESS)
1221 {
1222 JLOG(j_.debug()) << "final result: " << transToken(result);
1223 }
1224
1225 return {result, true};
1226 }
1227 }
1228
1229 // We need to place the remainder of the offer into its order book.
1230 auto const offer_index = keylet::offer(account_, offerSequence);
1231
1232 // Add offer to owner's directory.
1233 auto const ownerNode = sb.dirInsert(
1235
1236 if (!ownerNode)
1237 {
1238 JLOG(j_.debug())
1239 << "final result: failed to add offer to owner's directory";
1240 return {tecDIR_FULL, true};
1241 }
1242
1243 // Update owner count.
1244 adjustOwnerCount(sb, sleCreator, 1, viewJ);
1245
1246 JLOG(j_.trace()) << "adding to book: " << to_string(saTakerPays.issue())
1247 << " : " << to_string(saTakerGets.issue())
1248 << (domainID ? (" : " + to_string(*domainID)) : "");
1249
1250 Book const book{saTakerPays.issue(), saTakerGets.issue(), domainID};
1251
1252 // Add offer to order book, using the original rate
1253 // before any crossing occured.
1254 //
1255 // Regular offer - BookDirectory points to open directory
1256 //
1257 // Domain offer (w/o hyrbid) - BookDirectory points to domain
1258 // directory
1259 //
1260 // Hybrid domain offer - BookDirectory points to domain directory,
1261 // and AdditionalBooks field stores one entry that points to the open
1262 // directory
1263 auto dir = keylet::quality(keylet::book(book), uRate);
1264 bool const bookExisted = static_cast<bool>(sb.peek(dir));
1265
1266 auto setBookDir = [&](SLE::ref sle,
1267 std::optional<uint256> const& maybeDomain) {
1268 sle->setFieldH160(sfTakerPaysCurrency, saTakerPays.issue().currency);
1269 sle->setFieldH160(sfTakerPaysIssuer, saTakerPays.issue().account);
1270 sle->setFieldH160(sfTakerGetsCurrency, saTakerGets.issue().currency);
1271 sle->setFieldH160(sfTakerGetsIssuer, saTakerGets.issue().account);
1272 sle->setFieldU64(sfExchangeRate, uRate);
1273 if (maybeDomain)
1274 sle->setFieldH256(sfDomainID, *maybeDomain);
1275 };
1276
1277 auto const bookNode = sb.dirAppend(dir, offer_index, [&](SLE::ref sle) {
1278 // sets domainID on book directory if it's a domain offer
1279 setBookDir(sle, domainID);
1280 });
1281
1282 if (!bookNode)
1283 {
1284 JLOG(j_.debug()) << "final result: failed to add offer to book";
1285 return {tecDIR_FULL, true};
1286 }
1287
1288 auto sleOffer = std::make_shared<SLE>(offer_index);
1289 sleOffer->setAccountID(sfAccount, account_);
1290 sleOffer->setFieldU32(sfSequence, offerSequence);
1291 sleOffer->setFieldH256(sfBookDirectory, dir.key);
1292 sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
1293 sleOffer->setFieldAmount(sfTakerGets, saTakerGets);
1294 sleOffer->setFieldU64(sfOwnerNode, *ownerNode);
1295 sleOffer->setFieldU64(sfBookNode, *bookNode);
1296 if (expiration)
1297 sleOffer->setFieldU32(sfExpiration, *expiration);
1298 if (bPassive)
1299 sleOffer->setFlag(lsfPassive);
1300 if (bSell)
1301 sleOffer->setFlag(lsfSell);
1302 if (domainID)
1303 sleOffer->setFieldH256(sfDomainID, *domainID);
1304
1305 // if it's a hybrid offer, set hybrid flag, and create an open dir
1306 if (bHybrid)
1307 {
1308 auto const res = applyHybrid(
1309 sb, sleOffer, offer_index, saTakerPays, saTakerGets, setBookDir);
1310 if (res != tesSUCCESS)
1311 return {res, true}; // LCOV_EXCL_LINE
1312 }
1313
1314 sb.insert(sleOffer);
1315
1316 if (!bookExisted)
1318
1319 JLOG(j_.debug()) << "final result: success";
1320
1321 return {tesSUCCESS, true};
1322}
1323
1324TER
1326{
1327 // This is the ledger view that we work against. Transactions are applied
1328 // as we go on processing transactions.
1329 Sandbox sb(&ctx_.view());
1330
1331 // This is a ledger with just the fees paid and any unfunded or expired
1332 // offers we encounter removed. It's used when handling Fill-or-Kill offers,
1333 // if the order isn't going to be placed, to avoid wasting the work we did.
1334 Sandbox sbCancel(&ctx_.view());
1335
1336 auto const result = applyGuts(sb, sbCancel);
1337 if (result.second)
1338 sb.apply(ctx_.rawView());
1339 else
1340 sbCancel.apply(ctx_.rawView());
1341 return result.first;
1342}
1343
1344} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:60
Stream fatal() const
Definition: Journal.h:352
Stream error() const
Definition: Journal.h:346
Stream debug() const
Definition: Journal.h:328
Stream trace() const
Severity stream access functions.
Definition: Journal.h:322
virtual beast::Journal journal(std::string const &name)=0
virtual OrderBookDB & getOrderBookDB()=0
RawView & rawView()
Definition: ApplyContext.h:91
ApplyView & view()
Definition: ApplyContext.h:78
Application & app
Definition: ApplyContext.h:71
Writeable view to a ledger, for applying a transaction.
Definition: ApplyView.h:144
std::optional< std::uint64_t > dirAppend(Keylet const &directory, Keylet const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Append an entry to a directory.
Definition: ApplyView.h:281
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition: ApplyView.h:318
bool done() const
Returns true if order crossing should not continue.
Definition: Taker.cpp:123
AccountID const & account() const noexcept
Returns the account identifier of the taker.
Definition: Taker.h:171
bool reject(Quality const &quality) const noexcept
Returns true if the quality does not meet the taker's requirements.
Definition: Taker.h:178
Issue const & issue_in() const
Returns the Issue associated with the input of the offer.
Definition: Taker.h:192
Amounts remaining_offer() const
Returns the amount remaining on the offer.
Definition: Taker.cpp:152
Amounts const & original_offer() const
Returns the amount that the offer was originally placed at.
Definition: Taker.cpp:185
Issue const & issue_out() const
Returns the Issue associated with the output of the offer.
Definition: Taker.h:199
Specifies an order book.
Definition: Book.h:36
std::pair< TER, Amounts > flowCross(PaymentSandbox &psb, PaymentSandbox &psbCancel, Amounts const &takerAmount, std::optional< uint256 > const &domainID)
std::pair< TER, Amounts > bridged_cross(Taker &taker, ApplyView &view, ApplyView &view_cancel, NetClock::time_point const when)
OfferStream::StepCounter stepCounter_
Definition: CreateOffer.h:148
static TER checkAcceptAsset(ReadView const &view, ApplyFlags const flags, AccountID const id, beast::Journal const j, Issue const &issue)
void preCompute() override
Gather information beyond what the Transactor base class gathers.
bool dry_offer(ApplyView &view, Offer const &offer)
static TER preclaim(PreclaimContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
static bool step_account(OfferStream &stream, Taker const &taker)
static std::string format_amount(STAmount const &amount)
bool reachedOfferCrossingLimit(Taker const &taker) const
std::pair< TER, Amounts > direct_cross(Taker &taker, ApplyView &view, ApplyView &view_cancel, NetClock::time_point const when)
static NotTEC preflight(PreflightContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
Definition: CreateOffer.cpp:47
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
Definition: CreateOffer.cpp:36
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)
CrossType cross_type_
Definition: CreateOffer.h:145
TER doApply() override
Precondition: fee collection is likely.
std::pair< TER, bool > applyGuts(Sandbox &view, Sandbox &view_cancel)
static std::pair< bool, Quality > select_path(bool have_direct, OfferStream const &direct, bool have_bridge, OfferStream const &leg1, OfferStream const &leg2)
std::pair< TER, Amounts > cross(Sandbox &sb, Sandbox &sbCancel, Amounts const &takerAmount, std::optional< uint256 > const &domainID)
A currency issued by an account.
Definition: Issue.h:33
AccountID account
Definition: Issue.h:36
Currency currency
Definition: Issue.h:35
Presents and consumes the offers in an order book.
Definition: OfferStream.h:148
void addOrderBook(Book const &)
A wrapper which makes credits unavailable to balances.
A view into a ledger.
Definition: ReadView.h:52
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
static int const cMaxOffset
Definition: STAmount.h:66
Currency const & getCurrency() const
Definition: STAmount.h:502
static std::uint64_t const cMaxValue
Definition: STAmount.h:70
std::string getText() const override
Definition: STAmount.cpp:706
AccountID const & getIssuer() const
Definition: STAmount.h:508
Issue const & issue() const
Definition: STAmount.h:496
static std::uint64_t const cMaxNative
Definition: STAmount.h:71
std::string getFullText() const override
Definition: STAmount.cpp:696
bool native() const noexcept
Definition: STAmount.h:458
void push_back(STObject const &object)
Definition: STArray.h:212
STAmount const & getFieldAmount(SField const &field) const
Definition: STObject.cpp:665
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:484
static STObject makeInnerObject(SField const &name)
Definition: STObject.cpp:95
std::uint32_t getFlags() const
Definition: STObject.cpp:537
void emplace_back(Args &&... args)
Definition: STPathSet.h:521
std::uint32_t getSeqValue() const
Returns the first non-zero value of (Sequence, TicketSequence).
Definition: STTx.cpp:231
Discardable, editable view to a ledger.
Definition: Sandbox.h:35
void apply(RawView &to)
Definition: Sandbox.h:55
bool step()
Advance to the next valid offer.
TOffer< TIn, TOut > & tip() const
Returns the offer at the tip of the order book.
Definition: OfferStream.h:108
Quality quality() const noexcept
Returns the quality of the offer.
Definition: Offer.h:78
AccountID const & owner() const
Returns the account id of the offer's owner.
Definition: Offer.h:85
TAmounts< TIn, TOut > const & amount() const
Returns the in and out amounts.
Definition: Offer.h:94
std::uint32_t get_bridge_crossings() const
Definition: Taker.h:273
std::uint32_t get_direct_crossings() const
Definition: Taker.h:267
TER cross(Offer &offer)
Perform a direct or bridged offer crossing as appropriate.
Definition: Taker.cpp:822
AccountID const account_
Definition: Transactor.h:143
ApplyView & view()
Definition: Transactor.h:159
beast::Journal const j_
Definition: Transactor.h:141
XRPAmount mPriorBalance
Definition: Transactor.h:144
virtual void preCompute()
Definition: Transactor.cpp:533
ApplyContext & ctx_
Definition: Transactor.h:140
Class describing the consequences to the account of applying a transaction if the transaction consume...
Definition: applySteps.h:59
Fees const & fees() const override
Returns the fees for the base ledger.
bool open() const override
Returns true if this reflects an open ledger.
void insert(std::shared_ptr< SLE > const &sle) override
Insert a new state SLE.
bool exists(Keylet const &k) const override
Determine if a state item exists.
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
Rules const & rules() const override
Returns the tx processing rules.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
T make_pair(T... args)
T min(T... args)
Keylet quality(Keylet const &k, std::uint64_t q) noexcept
The initial directory page for a specific quality.
Definition: Indexes.cpp:280
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition: Indexes.cpp:244
static book_t const book
Definition: Indexes.h:105
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:184
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:374
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition: Indexes.cpp:274
bool accountInDomain(ReadView const &view, AccountID const &account, Domain const &domainID)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:25
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition: Issue.h:115
STAmount divide(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:93
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
Definition: UintTypes.cpp:133
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition: View.cpp:553
@ fhZERO_IF_FROZEN
Definition: View.h:78
@ fhIGNORE_FREEZE
Definition: View.h:78
bool isXRP(AccountID const &c)
Definition: AccountID.h:90
@ telFAILED_PROCESSING
Definition: TER.h:56
constexpr std::uint32_t tfOfferCreateMask
Definition: TxFlags.h:103
STAmount divRoundStrict(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
Definition: STAmount.cpp:1761
bool isLegalNet(STAmount const &value)
Definition: STAmount.h:600
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition: View.cpp:761
@ lsfHighDeepFreeze
@ lsfRequireAuth
@ lsfLowDeepFreeze
constexpr std::uint32_t tfHybrid
Definition: TxFlags.h:102
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:53
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition: View.cpp:1049
constexpr std::uint32_t tfFillOrKill
Definition: TxFlags.h:100
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:91
STAmount divideRound(STAmount const &amount, Rate const &rate, bool roundUp)
Definition: Rate2.cpp:104
StrandResult< TInAmt, TOutAmt > flow(PaymentSandbox const &baseView, Strand const &strand, std::optional< TInAmt > const &maxIn, TOutAmt const &out, beast::Journal j)
Request out amount from a strand.
Definition: StrandFlow.h:105
constexpr std::uint32_t tfPassive
Definition: TxFlags.h:98
constexpr std::uint32_t tfImmediateOrCancel
Definition: TxFlags.h:99
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition: View.cpp:1471
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition: STAmount.cpp:486
@ tefINTERNAL
Definition: TER.h:173
OfferCrossing
Definition: Steps.h:45
@ yes
Definition: Steps.h:45
@ sell
Definition: Steps.h:45
Quality composed_quality(Quality const &lhs, Quality const &rhs)
Definition: Quality.cpp:158
static bool adjustOwnerCount(ApplyContext &ctx, int count)
Definition: SetOracle.cpp:186
std::string transToken(TER code)
Definition: TER.cpp:264
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:160
Currency const & xrpCurrency()
XRP currency.
Definition: UintTypes.cpp:119
@ tecINSUF_RESERVE_OFFER
Definition: TER.h:289
@ tecNO_ISSUER
Definition: TER.h:299
@ tecDIR_FULL
Definition: TER.h:287
@ tecUNFUNDED_OFFER
Definition: TER.h:284
@ tecFROZEN
Definition: TER.h:303
@ tecKILLED
Definition: TER.h:316
@ tecINTERNAL
Definition: TER.h:310
@ tecNO_PERMISSION
Definition: TER.h:305
@ tecNO_LINE
Definition: TER.h:301
@ tecFAILED_PROCESSING
Definition: TER.h:286
@ tecEXPIRED
Definition: TER.h:314
@ tecNO_AUTH
Definition: TER.h:300
@ tesSUCCESS
Definition: TER.h:244
bool isTesSuccess(TER x) noexcept
Definition: TER.h:674
STAmount divRound(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
Definition: STAmount.cpp:1751
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)
Definition: STAmount.cpp:1644
STAmount multiplyRound(STAmount const &amount, Rate const &rate, bool roundUp)
Definition: Rate2.cpp:64
ApplyFlags
Definition: ApplyView.h:31
@ tapRETRY
Definition: ApplyView.h:40
constexpr std::uint32_t tfSell
Definition: TxFlags.h:101
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition: View.cpp:175
@ terNO_ACCOUNT
Definition: TER.h:217
@ terNO_AUTH
Definition: TER.h:218
@ terNO_LINE
Definition: TER.h:219
bool isTecClaim(TER x) noexcept
Definition: TER.h:680
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Definition: View.cpp:184
@ temBAD_ISSUER
Definition: TER.h:93
@ temBAD_AMOUNT
Definition: TER.h:89
@ temREDUNDANT
Definition: TER.h:112
@ temBAD_CURRENCY
Definition: TER.h:90
@ temBAD_SEQUENCE
Definition: TER.h:104
@ temBAD_EXPIRATION
Definition: TER.h:91
@ temBAD_OFFER
Definition: TER.h:95
@ temINVALID_FLAG
Definition: TER.h:111
@ temDISABLED
Definition: TER.h:114
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
Definition: protocol/Fees.h:49
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:39
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:79
ReadView const & view
Definition: Transactor.h:82
Application & app
Definition: Transactor.h:81
beast::Journal const j
Definition: Transactor.h:87
State information when preflighting a tx.
Definition: Transactor.h:34
beast::Journal const j
Definition: Transactor.h:41
Represents a transfer rate.
Definition: Rate.h:40
T tie(T... args)
T what(T... args)