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