diff --git a/CreateOffer_8cpp_source.html b/CreateOffer_8cpp_source.html index 1cb1ce030d..ab27beaff0 100644 --- a/CreateOffer_8cpp_source.html +++ b/CreateOffer_8cpp_source.html @@ -104,9 +104,9 @@ $(function() {
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>
+
29#include <xrpl/protocol/STAmount.h>
+
30#include <xrpl/protocol/TER.h>
+
31#include <xrpl/protocol/TxFlags.h>
32#include <xrpl/protocol/st.h>
33
34namespace ripple {
@@ -389,1037 +389,644 @@ $(function() {
311 return tesSUCCESS;
312}
313
-
314bool
-
315CreateOffer::dry_offer(ApplyView& view, Offer const& offer)
-
316{
-
317 if (offer.fully_consumed())
-
318 return true;
-
319 auto const amount = accountFunds(
-
320 view,
-
321 offer.owner(),
-
322 offer.amount().out,
-
323 fhZERO_IF_FROZEN,
-
324 ctx_.app.journal("View"));
-
325 return (amount <= beast::zero);
-
326}
-
327
-
328std::pair<bool, Quality>
-
329CreateOffer::select_path(
-
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
-
364CreateOffer::reachedOfferCrossingLimit(Taker const& taker) const
-
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
-
374std::pair<TER, Amounts>
-
375CreateOffer::bridged_cross(
-
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,
-
395 stepCounter_,
-
396 j_);
-
397
-
398 OfferStream offers_leg1(
-
399 view,
-
400 view_cancel,
-
401 Book(taker.issue_in(), xrpIssue(), std::nullopt),
-
402 when,
-
403 stepCounter_,
-
404 j_);
-
405
-
406 OfferStream offers_leg2(
-
407 view,
-
408 view_cancel,
-
409 Book(xrpIssue(), taker.issue_out(), std::nullopt),
-
410 when,
-
411 stepCounter_,
-
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,
-
457 fhIGNORE_FREEZE,
-
458 viewJ);
-
459 }
-
460
-
461 cross_result = taker.cross(offers_direct.tip());
-
462
-
463 JLOG(j_.debug()) << "Direct Result: " << transToken(cross_result);
+
314std::pair<TER, Amounts>
+
315CreateOffer::flowCross(
+
316 PaymentSandbox& psb,
+
317 PaymentSandbox& psbCancel,
+
318 Amounts const& takerAmount,
+
319 std::optional<uint256> const& domainID)
+
320{
+
321 try
+
322 {
+
323 // If the taker is unfunded before we begin crossing there's nothing
+
324 // to do - just return an error.
+
325 //
+
326 // We check this in preclaim, but when selling XRP charged fees can
+
327 // cause a user's available balance to go to 0 (by causing it to dip
+
328 // below the reserve) so we check this case again.
+
329 STAmount const inStartBalance =
+
330 accountFunds(psb, account_, takerAmount.in, fhZERO_IF_FROZEN, j_);
+
331 if (inStartBalance <= beast::zero)
+
332 {
+
333 // The account balance can't cover even part of the offer.
+
334 JLOG(j_.debug()) << "Not crossing: taker is unfunded.";
+
335 return {tecUNFUNDED_OFFER, takerAmount};
+
336 }
+
337
+
338 // If the gateway has a transfer rate, accommodate that. The
+
339 // gateway takes its cut without any special consent from the
+
340 // offer taker. Set sendMax to allow for the gateway's cut.
+
341 Rate gatewayXferRate{QUALITY_ONE};
+
342 STAmount sendMax = takerAmount.in;
+
343 if (!sendMax.native() && (account_ != sendMax.getIssuer()))
+
344 {
+
345 gatewayXferRate = transferRate(psb, sendMax.getIssuer());
+
346 if (gatewayXferRate.value != QUALITY_ONE)
+
347 {
+
348 sendMax = multiplyRound(
+
349 takerAmount.in,
+
350 gatewayXferRate,
+
351 takerAmount.in.issue(),
+
352 true);
+
353 }
+
354 }
+
355
+
356 // Payment flow code compares quality after the transfer rate is
+
357 // included. Since transfer rate is incorporated compute threshold.
+
358 Quality threshold{takerAmount.out, sendMax};
+
359
+
360 // If we're creating a passive offer adjust the threshold so we only
+
361 // cross offers that have a better quality than this one.
+
362 std::uint32_t const txFlags = ctx_.tx.getFlags();
+
363 if (txFlags & tfPassive)
+
364 ++threshold;
+
365
+
366 // Don't send more than our balance.
+
367 if (sendMax > inStartBalance)
+
368 sendMax = inStartBalance;
+
369
+
370 // Always invoke flow() with the default path. However if neither
+
371 // of the takerAmount currencies are XRP then we cross through an
+
372 // additional path with XRP as the intermediate between two books.
+
373 // This second path we have to build ourselves.
+
374 STPathSet paths;
+
375 if (!takerAmount.in.native() && !takerAmount.out.native())
+
376 {
+
377 STPath path;
+
378 path.emplace_back(std::nullopt, xrpCurrency(), std::nullopt);
+
379 paths.emplace_back(std::move(path));
+
380 }
+
381 // Special handling for the tfSell flag.
+
382 STAmount deliver = takerAmount.out;
+
383 OfferCrossing offerCrossing = OfferCrossing::yes;
+
384 if (txFlags & tfSell)
+
385 {
+
386 offerCrossing = OfferCrossing::sell;
+
387 // We are selling, so we will accept *more* than the offer
+
388 // specified. Since we don't know how much they might offer,
+
389 // we allow delivery of the largest possible amount.
+
390 if (deliver.native())
+
391 deliver = STAmount{STAmount::cMaxNative};
+
392 else
+
393 // We can't use the maximum possible currency here because
+
394 // there might be a gateway transfer rate to account for.
+
395 // Since the transfer rate cannot exceed 200%, we use 1/2
+
396 // maxValue for our limit.
+
397 deliver = STAmount{
+
398 takerAmount.out.issue(),
+
399 STAmount::cMaxValue / 2,
+
400 STAmount::cMaxOffset};
+
401 }
+
402
+
403 // Call the payment engine's flow() to do the actual work.
+
404 auto const result = flow(
+
405 psb,
+
406 deliver,
+
407 account_,
+
408 account_,
+
409 paths,
+
410 true, // default path
+
411 !(txFlags & tfFillOrKill), // partial payment
+
412 true, // owner pays transfer fee
+
413 offerCrossing,
+
414 threshold,
+
415 sendMax,
+
416 domainID,
+
417 j_);
+
418
+
419 // If stale offers were found remove them.
+
420 for (auto const& toRemove : result.removableOffers)
+
421 {
+
422 if (auto otr = psb.peek(keylet::offer(toRemove)))
+
423 offerDelete(psb, otr, j_);
+
424 if (auto otr = psbCancel.peek(keylet::offer(toRemove)))
+
425 offerDelete(psbCancel, otr, j_);
+
426 }
+
427
+
428 // Determine the size of the final offer after crossing.
+
429 auto afterCross = takerAmount; // If !tesSUCCESS offer unchanged
+
430 if (isTesSuccess(result.result()))
+
431 {
+
432 STAmount const takerInBalance = accountFunds(
+
433 psb, account_, takerAmount.in, fhZERO_IF_FROZEN, j_);
+
434
+
435 if (takerInBalance <= beast::zero)
+
436 {
+
437 // If offer crossing exhausted the account's funds don't
+
438 // create the offer.
+
439 afterCross.in.clear();
+
440 afterCross.out.clear();
+
441 }
+
442 else
+
443 {
+
444 STAmount const rate{
+
445 Quality{takerAmount.out, takerAmount.in}.rate()};
+
446
+
447 if (txFlags & tfSell)
+
448 {
+
449 // If selling then scale the new out amount based on how
+
450 // much we sold during crossing. This preserves the offer
+
451 // Quality,
+
452
+
453 // Reduce the offer that is placed by the crossed amount.
+
454 // Note that we must ignore the portion of the
+
455 // actualAmountIn that may have been consumed by a
+
456 // gateway's transfer rate.
+
457 STAmount nonGatewayAmountIn = result.actualAmountIn;
+
458 if (gatewayXferRate.value != QUALITY_ONE)
+
459 nonGatewayAmountIn = divideRound(
+
460 result.actualAmountIn,
+
461 gatewayXferRate,
+
462 takerAmount.in.issue(),
+
463 true);
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,
-
479 fhIGNORE_FREEZE,
-
480 viewJ);
-
481
-
482 auto const owner2_funds_before = accountFunds(
-
483 view,
-
484 offers_leg2.tip().owner(),
-
485 offers_leg2.tip().amount().out,
-
486 fhIGNORE_FREEZE,
-
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
-
567std::pair<TER, Amounts>
-
568CreateOffer::direct_cross(
-
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,
-
579 stepCounter_,
-
580 j_);
+
465 afterCross.in -= nonGatewayAmountIn;
+
466
+
467 // It's possible that the divRound will cause our subtract
+
468 // to go slightly negative. So limit afterCross.in to zero.
+
469 if (afterCross.in < beast::zero)
+
470 // We should verify that the difference *is* small, but
+
471 // what is a good threshold to check?
+
472 afterCross.in.clear();
+
473
+
474 afterCross.out = [&]() {
+
475 // Careful analysis showed that rounding up this
+
476 // divRound result could lead to placing a reduced
+
477 // offer in the ledger that blocks order books. So
+
478 // the fixReducedOffersV1 amendment changes the
+
479 // behavior to round down instead.
+
480 if (psb.rules().enabled(fixReducedOffersV1))
+
481 return divRoundStrict(
+
482 afterCross.in,
+
483 rate,
+
484 takerAmount.out.issue(),
+
485 false);
+
486
+
487 return divRound(
+
488 afterCross.in, rate, takerAmount.out.issue(), true);
+
489 }();
+
490 }
+
491 else
+
492 {
+
493 // If not selling, we scale the input based on the
+
494 // remaining output. This too preserves the offer
+
495 // Quality.
+
496 afterCross.out -= result.actualAmountOut;
+
497 XRPL_ASSERT(
+
498 afterCross.out >= beast::zero,
+
499 "ripple::CreateOffer::flowCross : minimum offer");
+
500 if (afterCross.out < beast::zero)
+
501 afterCross.out.clear();
+
502 afterCross.in = mulRound(
+
503 afterCross.out, rate, takerAmount.in.issue(), true);
+
504 }
+
505 }
+
506 }
+
507
+
508 // Return how much of the offer is left.
+
509 return {tesSUCCESS, afterCross};
+
510 }
+
511 catch (std::exception const& e)
+
512 {
+
513 JLOG(j_.error()) << "Exception during offer crossing: " << e.what();
+
514 }
+
515 return {tecINTERNAL, takerAmount};
+
516}
+
517
+
518std::string
+
519CreateOffer::format_amount(STAmount const& amount)
+
520{
+
521 std::string txt = amount.getText();
+
522 txt += "/";
+
523 txt += to_string(amount.issue().currency);
+
524 return txt;
+
525}
+
526
+
527TER
+
528CreateOffer::applyHybrid(
+
529 Sandbox& sb,
+
530 std::shared_ptr<STLedgerEntry> sleOffer,
+
531 Keylet const& offerKey,
+
532 STAmount const& saTakerPays,
+
533 STAmount const& saTakerGets,
+
534 std::function<void(SLE::ref, std::optional<uint256>)> const& setDir)
+
535{
+
536 if (!sleOffer->isFieldPresent(sfDomainID))
+
537 return tecINTERNAL; // LCOV_EXCL_LINE
+
538
+
539 // set hybrid flag
+
540 sleOffer->setFlag(lsfHybrid);
+
541
+
542 // if offer is hybrid, need to also place into open offer dir
+
543 Book const book{saTakerPays.issue(), saTakerGets.issue(), std::nullopt};
+
544
+
545 auto dir =
+
546 keylet::quality(keylet::book(book), getRate(saTakerGets, saTakerPays));
+
547 bool const bookExists = sb.exists(dir);
+
548
+
549 auto const bookNode = sb.dirAppend(dir, offerKey, [&](SLE::ref sle) {
+
550 // don't set domainID on the directory object since this directory is
+
551 // for open book
+
552 setDir(sle, std::nullopt);
+
553 });
+
554
+
555 if (!bookNode)
+
556 {
+
557 JLOG(j_.debug())
+
558 << "final result: failed to add hybrid offer to open book";
+
559 return tecDIR_FULL; // LCOV_EXCL_LINE
+
560 }
+
561
+
562 STArray bookArr(sfAdditionalBooks, 1);
+
563 auto bookInfo = STObject::makeInnerObject(sfBook);
+
564 bookInfo.setFieldH256(sfBookDirectory, dir.key);
+
565 bookInfo.setFieldU64(sfBookNode, *bookNode);
+
566 bookArr.push_back(std::move(bookInfo));
+
567
+
568 if (!bookExists)
+
569 ctx_.app.getOrderBookDB().addOrderBook(book);
+
570
+
571 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
+
572 return tesSUCCESS;
+
573}
+
574
+
575std::pair<TER, bool>
+
576CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel)
+
577{
+
578 using beast::zero;
+
579
+
580 std::uint32_t const uTxFlags = ctx_.tx.getFlags();
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());
+
582 bool const bPassive(uTxFlags & tfPassive);
+
583 bool const bImmediateOrCancel(uTxFlags & tfImmediateOrCancel);
+
584 bool const bFillOrKill(uTxFlags & tfFillOrKill);
+
585 bool const bSell(uTxFlags & tfSell);
+
586 bool const bHybrid(uTxFlags & tfHybrid);
+
587
+
588 auto saTakerPays = ctx_.tx[sfTakerPays];
+
589 auto saTakerGets = ctx_.tx[sfTakerGets];
+
590 auto const domainID = ctx_.tx[~sfDomainID];
+
591
+
592 auto const cancelSequence = ctx_.tx[~sfOfferSequence];
593
-
594 // We are done with crossing as soon as we cross the quality boundary
-
595 if (taker.reject(offer.quality()))
-
596 break;
+
594 // Note that we we use the value from the sequence or ticket as the
+
595 // offer sequence. For more explanation see comments in SeqProxy.h.
+
596 auto const offerSequence = ctx_.tx.getSeqValue();
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,
-
613 fhIGNORE_FREEZE,
-
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
-
663CreateOffer::step_account(OfferStream& stream, Taker const& taker)
-
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
-
682std::pair<TER, Amounts>
-
683CreateOffer::flowCross(
-
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};
+
598 // This is the original rate of the offer, and is the rate at which
+
599 // it will be placed, even if crossing offers change the amounts that
+
600 // end up on the books.
+
601 auto uRate = getRate(saTakerGets, saTakerPays);
+
602
+
603 auto viewJ = ctx_.app.journal("View");
+
604
+
605 TER result = tesSUCCESS;
+
606
+
607 // Process a cancellation request that's passed along with an offer.
+
608 if (cancelSequence)
+
609 {
+
610 auto const sleCancel =
+
611 sb.peek(keylet::offer(account_, *cancelSequence));
+
612
+
613 // It's not an error to not find the offer to cancel: it might have
+
614 // been consumed or removed. If it is found, however, it's an error
+
615 // to fail to delete it.
+
616 if (sleCancel)
+
617 {
+
618 JLOG(j_.debug()) << "Create cancels order " << *cancelSequence;
+
619 result = offerDelete(sb, sleCancel, viewJ);
+
620 }
+
621 }
+
622
+
623 auto const expiration = ctx_.tx[~sfExpiration];
+
624
+
625 if (hasExpired(sb, expiration))
+
626 {
+
627 // If the offer has expired, the transaction has successfully
+
628 // done nothing, so short circuit from here.
+
629 //
+
630 // The return code change is attached to featureDepositPreauth as a
+
631 // convenience. The change is not big enough to deserve a fix code.
+
632 TER const ter{
+
633 sb.rules().enabled(featureDepositPreauth) ? TER{tecEXPIRED}
+
634 : TER{tesSUCCESS}};
+
635 return {ter, true};
+
636 }
+
637
+
638 bool const bOpenLedger = sb.open();
+
639 bool crossed = false;
+
640
+
641 if (result == tesSUCCESS)
+
642 {
+
643 // If a tick size applies, round the offer to the tick size
+
644 auto const& uPaysIssuerID = saTakerPays.getIssuer();
+
645 auto const& uGetsIssuerID = saTakerGets.getIssuer();
+
646
+
647 std::uint8_t uTickSize = Quality::maxTickSize;
+
648 if (!isXRP(uPaysIssuerID))
+
649 {
+
650 auto const sle = sb.read(keylet::account(uPaysIssuerID));
+
651 if (sle && sle->isFieldPresent(sfTickSize))
+
652 uTickSize = std::min(uTickSize, (*sle)[sfTickSize]);
+
653 }
+
654 if (!isXRP(uGetsIssuerID))
+
655 {
+
656 auto const sle = sb.read(keylet::account(uGetsIssuerID));
+
657 if (sle && sle->isFieldPresent(sfTickSize))
+
658 uTickSize = std::min(uTickSize, (*sle)[sfTickSize]);
+
659 }
+
660 if (uTickSize < Quality::maxTickSize)
+
661 {
+
662 auto const rate =
+
663 Quality{saTakerGets, saTakerPays}.round(uTickSize).rate();
+
664
+
665 // We round the side that's not exact,
+
666 // just as if the offer happened to execute
+
667 // at a slightly better (for the placer) rate
+
668 if (bSell)
+
669 {
+
670 // this is a sell, round taker pays
+
671 saTakerPays = multiply(saTakerGets, rate, saTakerPays.issue());
+
672 }
+
673 else
+
674 {
+
675 // this is a buy, round taker gets
+
676 saTakerGets = divide(saTakerPays, rate, saTakerGets.issue());
+
677 }
+
678 if (!saTakerGets || !saTakerPays)
+
679 {
+
680 JLOG(j_.debug()) << "Offer rounded to zero";
+
681 return {result, true};
+
682 }
+
683
+
684 uRate = getRate(saTakerGets, saTakerPays);
+
685 }
+
686
+
687 // We reverse pays and gets because during crossing we are taking.
+
688 Amounts const takerAmount(saTakerGets, saTakerPays);
+
689
+
690 JLOG(j_.debug()) << "Attempting cross: "
+
691 << to_string(takerAmount.in.issue()) << " -> "
+
692 << to_string(takerAmount.out.issue());
+
693
+
694 if (auto stream = j_.trace())
+
695 {
+
696 stream << " mode: " << (bPassive ? "passive " : "")
+
697 << (bSell ? "sell" : "buy");
+
698 stream << " in: " << format_amount(takerAmount.in);
+
699 stream << " out: " << format_amount(takerAmount.out);
+
700 }
+
701
+
702 // The amount of the offer that is unfilled after crossing has been
+
703 // performed. It may be equal to the original amount (didn't cross),
+
704 // empty (fully crossed), or something in-between.
+
705 Amounts place_offer;
+
706 PaymentSandbox psbFlow{&sb};
+
707 PaymentSandbox psbCancelFlow{&sbCancel};
+
708
+
709 std::tie(result, place_offer) =
+
710 flowCross(psbFlow, psbCancelFlow, takerAmount, domainID);
+
711 psbFlow.apply(sb);
+
712 psbCancelFlow.apply(sbCancel);
+
713
+
714 // We expect the implementation of cross to succeed
+
715 // or give a tec.
+
716 XRPL_ASSERT(
+
717 result == tesSUCCESS || isTecClaim(result),
+
718 "ripple::CreateOffer::applyGuts : result is tesSUCCESS or "
+
719 "tecCLAIM");
+
720
+
721 if (auto stream = j_.trace())
+
722 {
+
723 stream << "Cross result: " << transToken(result);
+
724 stream << " in: " << format_amount(place_offer.in);
+
725 stream << " out: " << format_amount(place_offer.out);
+
726 }
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())
-
759 deliver = STAmount{STAmount::cMaxNative};
-
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(),
-
767 STAmount::cMaxValue / 2,
-
768 STAmount::cMaxOffset};
-
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_);
+
728 if (result == tecFAILED_PROCESSING && bOpenLedger)
+
729 result = telFAILED_PROCESSING;
+
730
+
731 if (result != tesSUCCESS)
+
732 {
+
733 JLOG(j_.debug()) << "final result: " << transToken(result);
+
734 return {result, true};
+
735 }
+
736
+
737 XRPL_ASSERT(
+
738 saTakerGets.issue() == place_offer.in.issue(),
+
739 "ripple::CreateOffer::applyGuts : taker gets issue match");
+
740 XRPL_ASSERT(
+
741 saTakerPays.issue() == place_offer.out.issue(),
+
742 "ripple::CreateOffer::applyGuts : taker pays issue match");
+
743
+
744 if (takerAmount != place_offer)
+
745 crossed = true;
+
746
+
747 // The offer that we need to place after offer crossing should
+
748 // never be negative. If it is, something went very very wrong.
+
749 if (place_offer.in < zero || place_offer.out < zero)
+
750 {
+
751 JLOG(j_.fatal()) << "Cross left offer negative!"
+
752 << " in: " << format_amount(place_offer.in)
+
753 << " out: " << format_amount(place_offer.out);
+
754 return {tefINTERNAL, true};
+
755 }
+
756
+
757 if (place_offer.in == zero || place_offer.out == zero)
+
758 {
+
759 JLOG(j_.debug()) << "Offer fully crossed!";
+
760 return {result, true};
+
761 }
+
762
+
763 // We now need to adjust the offer to reflect the amount left after
+
764 // crossing. We reverse in and out here, since during crossing we
+
765 // were the taker.
+
766 saTakerPays = place_offer.out;
+
767 saTakerGets = place_offer.in;
+
768 }
+
769
+
770 XRPL_ASSERT(
+
771 saTakerPays > zero && saTakerGets > zero,
+
772 "ripple::CreateOffer::applyGuts : taker pays and gets positive");
+
773
+
774 if (result != tesSUCCESS)
+
775 {
+
776 JLOG(j_.debug()) << "final result: " << transToken(result);
+
777 return {result, true};
+
778 }
+
779
+
780 if (auto stream = j_.trace())
+
781 {
+
782 stream << "Place" << (crossed ? " remaining " : " ") << "offer:";
+
783 stream << " Pays: " << saTakerPays.getFullText();
+
784 stream << " Gets: " << saTakerGets.getFullText();
+
785 }
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()};
+
787 // For 'fill or kill' offers, failure to fully cross means that the
+
788 // entire operation should be aborted, with only fees paid.
+
789 if (bFillOrKill)
+
790 {
+
791 JLOG(j_.trace()) << "Fill or Kill: offer killed";
+
792 if (sb.rules().enabled(fix1578))
+
793 return {tecKILLED, false};
+
794 return {tesSUCCESS, false};
+
795 }
+
796
+
797 // For 'immediate or cancel' offers, the amount remaining doesn't get
+
798 // placed - it gets canceled and the operation succeeds.
+
799 if (bImmediateOrCancel)
+
800 {
+
801 JLOG(j_.trace()) << "Immediate or cancel: offer canceled";
+
802 if (!crossed && sb.rules().enabled(featureImmediateOfferKilled))
+
803 // If the ImmediateOfferKilled amendment is enabled, any
+
804 // ImmediateOrCancel offer that transfers absolutely no funds
+
805 // returns tecKILLED rather than tesSUCCESS. Motivation for the
+
806 // change is here: https://github.com/ripple/rippled/issues/4115
+
807 return {tecKILLED, false};
+
808 return {tesSUCCESS, true};
+
809 }
+
810
+
811 auto const sleCreator = sb.peek(keylet::account(account_));
+
812 if (!sleCreator)
+
813 return {tefINTERNAL, false};
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
-
886std::pair<TER, Amounts>
-
887CreateOffer::cross(
-
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
-
901std::string
-
902CreateOffer::format_amount(STAmount const& amount)
-
903{
-
904 std::string txt = amount.getText();
-
905 txt += "/";
-
906 txt += to_string(amount.issue().currency);
-
907 return txt;
-
908}
-
909
-
910void
-
911CreateOffer::preCompute()
-
912{
-
913 cross_type_ = CrossType::IouToIou;
-
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)
-
917 cross_type_ = CrossType::IouToXrp;
-
918 else if (gets_xrp && !pays_xrp)
-
919 cross_type_ = CrossType::XrpToIou;
+
815 {
+
816 XRPAmount reserve =
+
817 sb.fees().accountReserve(sleCreator->getFieldU32(sfOwnerCount) + 1);
+
818
+
819 if (mPriorBalance < reserve)
+
820 {
+
821 // If we are here, the signing account had an insufficient reserve
+
822 // *prior* to our processing. If something actually crossed, then
+
823 // we allow this; otherwise, we just claim a fee.
+
824 if (!crossed)
+
825 result = tecINSUF_RESERVE_OFFER;
+
826
+
827 if (result != tesSUCCESS)
+
828 {
+
829 JLOG(j_.debug()) << "final result: " << transToken(result);
+
830 }
+
831
+
832 return {result, true};
+
833 }
+
834 }
+
835
+
836 // We need to place the remainder of the offer into its order book.
+
837 auto const offer_index = keylet::offer(account_, offerSequence);
+
838
+
839 // Add offer to owner's directory.
+
840 auto const ownerNode = sb.dirInsert(
+
841 keylet::ownerDir(account_), offer_index, describeOwnerDir(account_));
+
842
+
843 if (!ownerNode)
+
844 {
+
845 JLOG(j_.debug())
+
846 << "final result: failed to add offer to owner's directory";
+
847 return {tecDIR_FULL, true};
+
848 }
+
849
+
850 // Update owner count.
+
851 adjustOwnerCount(sb, sleCreator, 1, viewJ);
+
852
+
853 JLOG(j_.trace()) << "adding to book: " << to_string(saTakerPays.issue())
+
854 << " : " << to_string(saTakerGets.issue())
+
855 << (domainID ? (" : " + to_string(*domainID)) : "");
+
856
+
857 Book const book{saTakerPays.issue(), saTakerGets.issue(), domainID};
+
858
+
859 // Add offer to order book, using the original rate
+
860 // before any crossing occured.
+
861 //
+
862 // Regular offer - BookDirectory points to open directory
+
863 //
+
864 // Domain offer (w/o hyrbid) - BookDirectory points to domain
+
865 // directory
+
866 //
+
867 // Hybrid domain offer - BookDirectory points to domain directory,
+
868 // and AdditionalBooks field stores one entry that points to the open
+
869 // directory
+
870 auto dir = keylet::quality(keylet::book(book), uRate);
+
871 bool const bookExisted = static_cast<bool>(sb.peek(dir));
+
872
+
873 auto setBookDir = [&](SLE::ref sle,
+
874 std::optional<uint256> const& maybeDomain) {
+
875 sle->setFieldH160(sfTakerPaysCurrency, saTakerPays.issue().currency);
+
876 sle->setFieldH160(sfTakerPaysIssuer, saTakerPays.issue().account);
+
877 sle->setFieldH160(sfTakerGetsCurrency, saTakerGets.issue().currency);
+
878 sle->setFieldH160(sfTakerGetsIssuer, saTakerGets.issue().account);
+
879 sle->setFieldU64(sfExchangeRate, uRate);
+
880 if (maybeDomain)
+
881 sle->setFieldH256(sfDomainID, *maybeDomain);
+
882 };
+
883
+
884 auto const bookNode = sb.dirAppend(dir, offer_index, [&](SLE::ref sle) {
+
885 // sets domainID on book directory if it's a domain offer
+
886 setBookDir(sle, domainID);
+
887 });
+
888
+
889 if (!bookNode)
+
890 {
+
891 JLOG(j_.debug()) << "final result: failed to add offer to book";
+
892 return {tecDIR_FULL, true};
+
893 }
+
894
+
895 auto sleOffer = std::make_shared<SLE>(offer_index);
+
896 sleOffer->setAccountID(sfAccount, account_);
+
897 sleOffer->setFieldU32(sfSequence, offerSequence);
+
898 sleOffer->setFieldH256(sfBookDirectory, dir.key);
+
899 sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
+
900 sleOffer->setFieldAmount(sfTakerGets, saTakerGets);
+
901 sleOffer->setFieldU64(sfOwnerNode, *ownerNode);
+
902 sleOffer->setFieldU64(sfBookNode, *bookNode);
+
903 if (expiration)
+
904 sleOffer->setFieldU32(sfExpiration, *expiration);
+
905 if (bPassive)
+
906 sleOffer->setFlag(lsfPassive);
+
907 if (bSell)
+
908 sleOffer->setFlag(lsfSell);
+
909 if (domainID)
+
910 sleOffer->setFieldH256(sfDomainID, *domainID);
+
911
+
912 // if it's a hybrid offer, set hybrid flag, and create an open dir
+
913 if (bHybrid)
+
914 {
+
915 auto const res = applyHybrid(
+
916 sb, sleOffer, offer_index, saTakerPays, saTakerGets, setBookDir);
+
917 if (res != tesSUCCESS)
+
918 return {res, true}; // LCOV_EXCL_LINE
+
919 }
920
-
921 return Transactor::preCompute();
-
922}
-
923
-
924TER
-
925CreateOffer::applyHybrid(
-
926 Sandbox& sb,
-
927 std::shared_ptr<STLedgerEntry> sleOffer,
-
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)
-
966 ctx_.app.getOrderBookDB().addOrderBook(book);
-
967
-
968 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
-
969 return tesSUCCESS;
-
970}
-
971
-
972std::pair<TER, bool>
-
973CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel)
-
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(
-
1234 keylet::ownerDir(account_), offer_index, describeOwnerDir(account_));
-
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)
-
1317 ctx_.app.getOrderBookDB().addOrderBook(book);
-
1318
-
1319 JLOG(j_.debug()) << "final result: success";
-
1320
-
1321 return {tesSUCCESS, true};
-
1322}
-
1323
-
1324TER
-
1325CreateOffer::doApply()
-
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
+
921 sb.insert(sleOffer);
+
922
+
923 if (!bookExisted)
+
924 ctx_.app.getOrderBookDB().addOrderBook(book);
+
925
+
926 JLOG(j_.debug()) << "final result: success";
+
927
+
928 return {tesSUCCESS, true};
+
929}
+
930
+
931TER
+
932CreateOffer::doApply()
+
933{
+
934 // This is the ledger view that we work against. Transactions are applied
+
935 // as we go on processing transactions.
+
936 Sandbox sb(&ctx_.view());
+
937
+
938 // This is a ledger with just the fees paid and any unfunded or expired
+
939 // offers we encounter removed. It's used when handling Fill-or-Kill offers,
+
940 // if the order isn't going to be placed, to avoid wasting the work we did.
+
941 Sandbox sbCancel(&ctx_.view());
+
942
+
943 auto const result = applyGuts(sb, sbCancel);
+
944 if (result.second)
+
945 sb.apply(ctx_.rawView());
+
946 else
+
947 sbCancel.apply(ctx_.rawView());
+
948 return result.first;
+
949}
+
950
+
951} // namespace ripple
std::string
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:60
beast::Journal::fatal
Stream fatal() const
Definition: Journal.h:352
@@ -1432,40 +1039,21 @@ $(function() {
ripple::ApplyContext::view
ApplyView & view()
Definition: ApplyContext.h:78
ripple::ApplyContext::app
Application & app
Definition: ApplyContext.h:71
ripple::ApplyContext::tx
STTx const & tx
Definition: ApplyContext.h:72
-
ripple::ApplyView
Writeable view to a ledger, for applying a transaction.
Definition: ApplyView.h:144
ripple::ApplyView::dirAppend
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
ripple::ApplyView::dirInsert
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
-
ripple::BasicTaker::done
bool done() const
Returns true if order crossing should not continue.
Definition: Taker.cpp:123
-
ripple::BasicTaker::account
AccountID const & account() const noexcept
Returns the account identifier of the taker.
Definition: Taker.h:171
-
ripple::BasicTaker::reject
bool reject(Quality const &quality) const noexcept
Returns true if the quality does not meet the taker's requirements.
Definition: Taker.h:178
-
ripple::BasicTaker::issue_in
Issue const & issue_in() const
Returns the Issue associated with the input of the offer.
Definition: Taker.h:192
-
ripple::BasicTaker::remaining_offer
Amounts remaining_offer() const
Returns the amount remaining on the offer.
Definition: Taker.cpp:152
-
ripple::BasicTaker::original_offer
Amounts const & original_offer() const
Returns the amount that the offer was originally placed at.
Definition: Taker.cpp:185
-
ripple::BasicTaker::issue_out
Issue const & issue_out() const
Returns the Issue associated with the output of the offer.
Definition: Taker.h:199
ripple::Book
Specifies an order book.
Definition: Book.h:36
-
ripple::CreateOffer::flowCross
std::pair< TER, Amounts > flowCross(PaymentSandbox &psb, PaymentSandbox &psbCancel, Amounts const &takerAmount, std::optional< uint256 > const &domainID)
Definition: CreateOffer.cpp:683
-
ripple::CreateOffer::bridged_cross
std::pair< TER, Amounts > bridged_cross(Taker &taker, ApplyView &view, ApplyView &view_cancel, NetClock::time_point const when)
Definition: CreateOffer.cpp:375
-
ripple::CreateOffer::stepCounter_
OfferStream::StepCounter stepCounter_
Definition: CreateOffer.h:148
+
ripple::CreateOffer::flowCross
std::pair< TER, Amounts > flowCross(PaymentSandbox &psb, PaymentSandbox &psbCancel, Amounts const &takerAmount, std::optional< uint256 > const &domainID)
Definition: CreateOffer.cpp:315
ripple::CreateOffer::checkAcceptAsset
static TER checkAcceptAsset(ReadView const &view, ApplyFlags const flags, AccountID const id, beast::Journal const j, Issue const &issue)
Definition: CreateOffer.cpp:228
-
ripple::CreateOffer::preCompute
void preCompute() override
Gather information beyond what the Transactor base class gathers.
Definition: CreateOffer.cpp:911
-
ripple::CreateOffer::dry_offer
bool dry_offer(ApplyView &view, Offer const &offer)
Definition: CreateOffer.cpp:315
ripple::CreateOffer::preclaim
static TER preclaim(PreclaimContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
Definition: CreateOffer.cpp:143
-
ripple::CreateOffer::step_account
static bool step_account(OfferStream &stream, Taker const &taker)
Definition: CreateOffer.cpp:663
-
ripple::CreateOffer::format_amount
static std::string format_amount(STAmount const &amount)
Definition: CreateOffer.cpp:902
-
ripple::CreateOffer::reachedOfferCrossingLimit
bool reachedOfferCrossingLimit(Taker const &taker) const
Definition: CreateOffer.cpp:364
-
ripple::CreateOffer::direct_cross
std::pair< TER, Amounts > direct_cross(Taker &taker, ApplyView &view, ApplyView &view_cancel, NetClock::time_point const when)
Definition: CreateOffer.cpp:568
+
ripple::CreateOffer::format_amount
static std::string format_amount(STAmount const &amount)
Definition: CreateOffer.cpp:519
ripple::CreateOffer::preflight
static NotTEC preflight(PreflightContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
Definition: CreateOffer.cpp:47
ripple::CreateOffer::makeTxConsequences
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
Definition: CreateOffer.cpp:36
-
ripple::CreateOffer::applyHybrid
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)
Definition: CreateOffer.cpp:925
-
ripple::CreateOffer::cross_type_
CrossType cross_type_
Definition: CreateOffer.h:145
-
ripple::CreateOffer::doApply
TER doApply() override
Precondition: fee collection is likely.
Definition: CreateOffer.cpp:1325
-
ripple::CreateOffer::applyGuts
std::pair< TER, bool > applyGuts(Sandbox &view, Sandbox &view_cancel)
Definition: CreateOffer.cpp:973
-
ripple::CreateOffer::select_path
static std::pair< bool, Quality > select_path(bool have_direct, OfferStream const &direct, bool have_bridge, OfferStream const &leg1, OfferStream const &leg2)
Definition: CreateOffer.cpp:329
-
ripple::CreateOffer::cross
std::pair< TER, Amounts > cross(Sandbox &sb, Sandbox &sbCancel, Amounts const &takerAmount, std::optional< uint256 > const &domainID)
Definition: CreateOffer.cpp:887
+
ripple::CreateOffer::applyHybrid
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)
Definition: CreateOffer.cpp:528
+
ripple::CreateOffer::doApply
TER doApply() override
Precondition: fee collection is likely.
Definition: CreateOffer.cpp:932
+
ripple::CreateOffer::applyGuts
std::pair< TER, bool > applyGuts(Sandbox &view, Sandbox &view_cancel)
Definition: CreateOffer.cpp:576
ripple::Issue
A currency issued by an account.
Definition: Issue.h:33
ripple::Issue::account
AccountID account
Definition: Issue.h:36
ripple::Issue::currency
Currency currency
Definition: Issue.h:35
-
ripple::OfferStream
Presents and consumes the offers in an order book.
Definition: OfferStream.h:148
ripple::OrderBookDB::addOrderBook
void addOrderBook(Book const &)
Definition: OrderBookDB.cpp:179
ripple::PaymentSandbox
A wrapper which makes credits unavailable to balances.
Definition: PaymentSandbox.h:114
ripple::ReadView
A view into a ledger.
Definition: ReadView.h:52
@@ -1485,7 +1073,6 @@ $(function() {
ripple::STAmount::native
bool native() const noexcept
Definition: STAmount.h:458
ripple::STArray
Definition: STArray.h:29
ripple::STArray::push_back
void push_back(STObject const &object)
Definition: STArray.h:212
-
ripple::STObject::getFieldAmount
STAmount const & getFieldAmount(SField const &field) const
Definition: STObject.cpp:665
ripple::STObject::isFieldPresent
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:484
ripple::STObject::makeInnerObject
static STObject makeInnerObject(SField const &name)
Definition: STObject.cpp:95
ripple::STObject::getFlags
std::uint32_t getFlags() const
Definition: STObject.cpp:537
@@ -1497,21 +1084,10 @@ $(function() {
ripple::Sandbox
Discardable, editable view to a ledger.
Definition: Sandbox.h:35
ripple::Sandbox::apply
void apply(RawView &to)
Definition: Sandbox.h:55
ripple::TERSubset
Definition: TER.h:411
-
ripple::TOfferStreamBase::step
bool step()
Advance to the next valid offer.
Definition: OfferStream.cpp:226
-
ripple::TOfferStreamBase::tip
TOffer< TIn, TOut > & tip() const
Returns the offer at the tip of the order book.
Definition: OfferStream.h:108
-
ripple::TOffer
Definition: Offer.h:53
-
ripple::TOffer::quality
Quality quality() const noexcept
Returns the quality of the offer.
Definition: Offer.h:78
-
ripple::TOffer::owner
AccountID const & owner() const
Returns the account id of the offer's owner.
Definition: Offer.h:85
-
ripple::TOffer::amount
TAmounts< TIn, TOut > const & amount() const
Returns the in and out amounts.
Definition: Offer.h:94
-
ripple::Taker
Definition: Taker.h:240
-
ripple::Taker::get_bridge_crossings
std::uint32_t get_bridge_crossings() const
Definition: Taker.h:273
-
ripple::Taker::get_direct_crossings
std::uint32_t get_direct_crossings() const
Definition: Taker.h:267
-
ripple::Taker::cross
TER cross(Offer &offer)
Perform a direct or bridged offer crossing as appropriate.
Definition: Taker.cpp:822
ripple::Transactor::account_
AccountID const account_
Definition: Transactor.h:143
ripple::Transactor::view
ApplyView & view()
Definition: Transactor.h:159
ripple::Transactor::j_
beast::Journal const j_
Definition: Transactor.h:141
ripple::Transactor::mPriorBalance
XRPAmount mPriorBalance
Definition: Transactor.h:144
-
ripple::Transactor::preCompute
virtual void preCompute()
Definition: Transactor.cpp:533
ripple::Transactor::ctx_
ApplyContext & ctx_
Definition: Transactor.h:140
ripple::TxConsequences
Class describing the consequences to the account of applying a transaction if the transaction consume...
Definition: applySteps.h:59
ripple::XRPAmount
Definition: XRPAmount.h:43
@@ -1526,7 +1102,6 @@ $(function() {
std::exception
std::function
std::uint32_t
-
std::make_pair
T make_pair(T... args)
std::min
T min(T... args)
ripple::keylet::quality
Keylet quality(Keylet const &k, std::uint64_t q) noexcept
The initial directory page for a specific quality.
Definition: Indexes.cpp:280
ripple::keylet::line
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
@@ -1536,12 +1111,10 @@ $(function() {
ripple::keylet::offer
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition: Indexes.cpp:274
ripple::permissioned_dex::accountInDomain
bool accountInDomain(ReadView const &view, AccountID const &account, Domain const &domainID)
Definition: PermissionedDEXHelpers.cpp:27
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:25
-
ripple::xrpIssue
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition: Issue.h:115
ripple::divide
STAmount divide(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:93
ripple::badCurrency
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
Definition: UintTypes.cpp:133
ripple::accountFunds
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition: View.cpp:553
ripple::fhZERO_IF_FROZEN
@ fhZERO_IF_FROZEN
Definition: View.h:78
-
ripple::fhIGNORE_FREEZE
@ fhIGNORE_FREEZE
Definition: View.h:78
ripple::isXRP
bool isXRP(AccountID const &c)
Definition: AccountID.h:90
ripple::telFAILED_PROCESSING
@ telFAILED_PROCESSING
Definition: TER.h:56
ripple::tfOfferCreateMask
constexpr std::uint32_t tfOfferCreateMask
Definition: TxFlags.h:103
@@ -1571,7 +1144,6 @@ $(function() {
ripple::OfferCrossing
OfferCrossing
Definition: Steps.h:45
ripple::yes
@ yes
Definition: Steps.h:45
ripple::sell
@ sell
Definition: Steps.h:45
-
ripple::composed_quality
Quality composed_quality(Quality const &lhs, Quality const &rhs)
Definition: Quality.cpp:158
ripple::adjustOwnerCount
static bool adjustOwnerCount(ApplyContext &ctx, int count)
Definition: SetOracle.cpp:186
ripple::transToken
std::string transToken(TER code)
Definition: TER.cpp:264
ripple::preflight2
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:160
@@ -1602,9 +1174,6 @@ $(function() {
ripple::terNO_AUTH
@ terNO_AUTH
Definition: TER.h:218
ripple::terNO_LINE
@ terNO_LINE
Definition: TER.h:219
ripple::isTecClaim
bool isTecClaim(TER x) noexcept
Definition: TER.h:680
-
ripple::CrossType::XrpToIou
@ XrpToIou
-
ripple::CrossType::IouToIou
@ IouToIou
-
ripple::CrossType::IouToXrp
@ IouToXrp
ripple::isGlobalFrozen
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Definition: View.cpp:184
ripple::temBAD_ISSUER
@ temBAD_ISSUER
Definition: TER.h:93
ripple::temBAD_AMOUNT
@ temBAD_AMOUNT
Definition: TER.h:89
@@ -1632,7 +1201,6 @@ $(function() {
ripple::PreflightContext::tx
STTx const & tx
Definition: Transactor.h:37
ripple::Rate
Represents a transfer rate.
Definition: Rate.h:40
std::tie
T tie(T... args)
-
std::chrono::time_point
std::exception::what
T what(T... args)
diff --git a/CreateOffer_8h_source.html b/CreateOffer_8h_source.html index 51df045260..fed2c265c0 100644 --- a/CreateOffer_8h_source.html +++ b/CreateOffer_8h_source.html @@ -98,9 +98,9 @@ $(function() {
20#ifndef RIPPLE_TX_CREATEOFFER_H_INCLUDED
21#define RIPPLE_TX_CREATEOFFER_H_INCLUDED
22
-
23#include <xrpld/app/tx/detail/OfferStream.h>
-
24#include <xrpld/app/tx/detail/Taker.h>
-
25#include <xrpld/app/tx/detail/Transactor.h>
+
23#include <xrpld/app/tx/detail/Transactor.h>
+
24
+
25#include <xrpl/protocol/Quality.h>
26
27namespace ripple {
28
@@ -112,172 +112,97 @@ $(function() {
35public:
36 static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
37
-
39 explicit CreateOffer(ApplyContext& ctx)
-
40 : Transactor(ctx), stepCounter_(1000, j_)
-
41 {
-
42 }
-
43
-
44 static TxConsequences
-
45 makeTxConsequences(PreflightContext const& ctx);
-
46
-
48 static NotTEC
-
49 preflight(PreflightContext const& ctx);
-
50
-
52 static TER
-
53 preclaim(PreclaimContext const& ctx);
-
54
-
56 void
-
57 preCompute() override;
-
58
-
60 TER
-
61 doApply() override;
-
62
-
63private:
-
64 std::pair<TER, bool>
-
65 applyGuts(Sandbox& view, Sandbox& view_cancel);
-
66
-
67 // Determine if we are authorized to hold the asset we want to get.
-
68 static TER
-
69 checkAcceptAsset(
-
70 ReadView const& view,
-
71 ApplyFlags const flags,
-
72 AccountID const id,
-
73 beast::Journal const j,
-
74 Issue const& issue);
-
75
-
76 bool
-
77 dry_offer(ApplyView& view, Offer const& offer);
+
39 explicit CreateOffer(ApplyContext& ctx) : Transactor(ctx)
+
40 {
+
41 }
+
42
+
43 static TxConsequences
+
44 makeTxConsequences(PreflightContext const& ctx);
+
45
+
47 static NotTEC
+
48 preflight(PreflightContext const& ctx);
+
49
+
51 static TER
+
52 preclaim(PreclaimContext const& ctx);
+
53
+
55 TER
+
56 doApply() override;
+
57
+
58private:
+
59 std::pair<TER, bool>
+
60 applyGuts(Sandbox& view, Sandbox& view_cancel);
+
61
+
62 // Determine if we are authorized to hold the asset we want to get.
+
63 static TER
+
64 checkAcceptAsset(
+
65 ReadView const& view,
+
66 ApplyFlags const flags,
+
67 AccountID const id,
+
68 beast::Journal const j,
+
69 Issue const& issue);
+
70
+
71 // Use the payment flow code to perform offer crossing.
+
72 std::pair<TER, Amounts>
+
73 flowCross(
+
74 PaymentSandbox& psb,
+
75 PaymentSandbox& psbCancel,
+
76 Amounts const& takerAmount,
+
77 std::optional<uint256> const& domainID);
78
-
79 static std::pair<bool, Quality>
-
80 select_path(
-
81 bool have_direct,
-
82 OfferStream const& direct,
-
83 bool have_bridge,
-
84 OfferStream const& leg1,
-
85 OfferStream const& leg2);
-
86
-
87 std::pair<TER, Amounts>
-
88 bridged_cross(
-
89 Taker& taker,
-
90 ApplyView& view,
-
91 ApplyView& view_cancel,
-
92 NetClock::time_point const when);
+
79 static std::string
+
80 format_amount(STAmount const& amount);
+
81
+
82 TER
+
83 applyHybrid(
+
84 Sandbox& sb,
+
85 std::shared_ptr<STLedgerEntry> sleOffer,
+
86 Keylet const& offer_index,
+
87 STAmount const& saTakerPays,
+
88 STAmount const& saTakerGets,
+
89 std::function<void(SLE::ref, std::optional<uint256>)> const& setDir);
+
90};
+
91
+
92using OfferCreate = CreateOffer;
93
-
94 std::pair<TER, Amounts>
-
95 direct_cross(
-
96 Taker& taker,
-
97 ApplyView& view,
-
98 ApplyView& view_cancel,
-
99 NetClock::time_point const when);
-
100
-
101 // Step through the stream for as long as possible, skipping any offers
-
102 // that are from the taker or which cross the taker's threshold.
-
103 // Return false if the is no offer in the book, true otherwise.
-
104 static bool
-
105 step_account(OfferStream& stream, Taker const& taker);
-
106
-
107 // True if the number of offers that have been crossed
-
108 // exceeds the limit.
-
109 bool
-
110 reachedOfferCrossingLimit(Taker const& taker) const;
-
111
-
112 // Use the payment flow code to perform offer crossing.
-
113 std::pair<TER, Amounts>
-
114 flowCross(
-
115 PaymentSandbox& psb,
-
116 PaymentSandbox& psbCancel,
-
117 Amounts const& takerAmount,
-
118 std::optional<uint256> const& domainID);
-
119
-
120 // Temporary
-
121 // This is a central location that invokes both versions of cross
-
122 // so the results can be compared. Eventually this layer will be
-
123 // removed once flowCross is determined to be stable.
-
124 std::pair<TER, Amounts>
-
125 cross(
-
126 Sandbox& sb,
-
127 Sandbox& sbCancel,
-
128 Amounts const& takerAmount,
-
129 std::optional<uint256> const& domainID);
-
130
-
131 static std::string
-
132 format_amount(STAmount const& amount);
-
133
-
134 TER
-
135 applyHybrid(
-
136 Sandbox& sb,
-
137 std::shared_ptr<STLedgerEntry> sleOffer,
-
138 Keylet const& offer_index,
-
139 STAmount const& saTakerPays,
-
140 STAmount const& saTakerGets,
-
141 std::function<void(SLE::ref, std::optional<uint256>)> const& setDir);
-
142
-
143private:
-
144 // What kind of offer we are placing
-
145 CrossType cross_type_;
-
146
-
147 // The number of steps to take through order books while crossing
-
148 OfferStream::StepCounter stepCounter_;
-
149};
-
150
-
151using OfferCreate = CreateOffer;
-
152
-
153} // namespace ripple
-
154
-
155#endif
+
94} // namespace ripple
+
95
+
96#endif
std::string
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:60
ripple::ApplyContext
State information when applying a tx.
Definition: ApplyContext.h:37
-
ripple::ApplyView
Writeable view to a ledger, for applying a transaction.
Definition: ApplyView.h:144
ripple::CreateOffer
Transactor specialized for creating offers in the ledger.
Definition: CreateOffer.h:34
-
ripple::CreateOffer::flowCross
std::pair< TER, Amounts > flowCross(PaymentSandbox &psb, PaymentSandbox &psbCancel, Amounts const &takerAmount, std::optional< uint256 > const &domainID)
Definition: CreateOffer.cpp:683
-
ripple::CreateOffer::bridged_cross
std::pair< TER, Amounts > bridged_cross(Taker &taker, ApplyView &view, ApplyView &view_cancel, NetClock::time_point const when)
Definition: CreateOffer.cpp:375
-
ripple::CreateOffer::stepCounter_
OfferStream::StepCounter stepCounter_
Definition: CreateOffer.h:148
+
ripple::CreateOffer::flowCross
std::pair< TER, Amounts > flowCross(PaymentSandbox &psb, PaymentSandbox &psbCancel, Amounts const &takerAmount, std::optional< uint256 > const &domainID)
Definition: CreateOffer.cpp:315
ripple::CreateOffer::checkAcceptAsset
static TER checkAcceptAsset(ReadView const &view, ApplyFlags const flags, AccountID const id, beast::Journal const j, Issue const &issue)
Definition: CreateOffer.cpp:228
ripple::CreateOffer::CreateOffer
CreateOffer(ApplyContext &ctx)
Construct a Transactor subclass that creates an offer in the ledger.
Definition: CreateOffer.h:39
-
ripple::CreateOffer::preCompute
void preCompute() override
Gather information beyond what the Transactor base class gathers.
Definition: CreateOffer.cpp:911
-
ripple::CreateOffer::dry_offer
bool dry_offer(ApplyView &view, Offer const &offer)
Definition: CreateOffer.cpp:315
ripple::CreateOffer::preclaim
static TER preclaim(PreclaimContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
Definition: CreateOffer.cpp:143
-
ripple::CreateOffer::step_account
static bool step_account(OfferStream &stream, Taker const &taker)
Definition: CreateOffer.cpp:663
-
ripple::CreateOffer::format_amount
static std::string format_amount(STAmount const &amount)
Definition: CreateOffer.cpp:902
+
ripple::CreateOffer::format_amount
static std::string format_amount(STAmount const &amount)
Definition: CreateOffer.cpp:519
ripple::CreateOffer::ConsequencesFactory
static constexpr ConsequencesFactoryType ConsequencesFactory
Definition: CreateOffer.h:36
-
ripple::CreateOffer::reachedOfferCrossingLimit
bool reachedOfferCrossingLimit(Taker const &taker) const
Definition: CreateOffer.cpp:364
-
ripple::CreateOffer::direct_cross
std::pair< TER, Amounts > direct_cross(Taker &taker, ApplyView &view, ApplyView &view_cancel, NetClock::time_point const when)
Definition: CreateOffer.cpp:568
ripple::CreateOffer::preflight
static NotTEC preflight(PreflightContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
Definition: CreateOffer.cpp:47
ripple::CreateOffer::makeTxConsequences
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
Definition: CreateOffer.cpp:36
-
ripple::CreateOffer::applyHybrid
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)
Definition: CreateOffer.cpp:925
-
ripple::CreateOffer::cross_type_
CrossType cross_type_
Definition: CreateOffer.h:145
-
ripple::CreateOffer::doApply
TER doApply() override
Precondition: fee collection is likely.
Definition: CreateOffer.cpp:1325
-
ripple::CreateOffer::applyGuts
std::pair< TER, bool > applyGuts(Sandbox &view, Sandbox &view_cancel)
Definition: CreateOffer.cpp:973
-
ripple::CreateOffer::select_path
static std::pair< bool, Quality > select_path(bool have_direct, OfferStream const &direct, bool have_bridge, OfferStream const &leg1, OfferStream const &leg2)
Definition: CreateOffer.cpp:329
-
ripple::CreateOffer::cross
std::pair< TER, Amounts > cross(Sandbox &sb, Sandbox &sbCancel, Amounts const &takerAmount, std::optional< uint256 > const &domainID)
Definition: CreateOffer.cpp:887
+
ripple::CreateOffer::applyHybrid
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)
Definition: CreateOffer.cpp:528
+
ripple::CreateOffer::doApply
TER doApply() override
Precondition: fee collection is likely.
Definition: CreateOffer.cpp:932
+
ripple::CreateOffer::applyGuts
std::pair< TER, bool > applyGuts(Sandbox &view, Sandbox &view_cancel)
Definition: CreateOffer.cpp:576
ripple::Issue
A currency issued by an account.
Definition: Issue.h:33
-
ripple::OfferStream
Presents and consumes the offers in an order book.
Definition: OfferStream.h:148
ripple::PaymentSandbox
A wrapper which makes credits unavailable to balances.
Definition: PaymentSandbox.h:114
ripple::ReadView
A view into a ledger.
Definition: ReadView.h:52
ripple::STAmount
Definition: STAmount.h:50
ripple::Sandbox
Discardable, editable view to a ledger.
Definition: Sandbox.h:35
ripple::TERSubset
Definition: TER.h:411
-
ripple::TOffer
Definition: Offer.h:53
-
ripple::Taker
Definition: Taker.h:240
ripple::Transactor
Definition: Transactor.h:138
ripple::Transactor::ConsequencesFactoryType
ConsequencesFactoryType
Definition: Transactor.h:153
ripple::Transactor::Custom
@ Custom
Definition: Transactor.h:153
ripple::Transactor::view
ApplyView & view()
Definition: Transactor.h:159
-
ripple::Transactor::j_
beast::Journal const j_
Definition: Transactor.h:141
ripple::TxConsequences
Class describing the consequences to the account of applying a transaction if the transaction consume...
Definition: applySteps.h:59
ripple::base_uint< 160, detail::AccountIDTag >
std::function
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:25
ripple::ApplyFlags
ApplyFlags
Definition: ApplyView.h:31
-
ripple::CrossType
CrossType
The flavor of an offer crossing.
Definition: Taker.h:35
std::optional
std::pair
std::shared_ptr
ripple::Keylet
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:39
ripple::PreclaimContext
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:79
ripple::PreflightContext
State information when preflighting a tx.
Definition: Transactor.h:34
-
std::chrono::time_point