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
25#include <xrpl/basics/base_uint.h>
26#include <xrpl/beast/utility/WrappedSink.h>
27#include <xrpl/ledger/PaymentSandbox.h>
28#include <xrpl/protocol/Feature.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 {
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
46bool
48{
49 if (ctx.tx.isFieldPresent(sfDomainID) &&
50 !ctx.rules.enabled(featurePermissionedDEX))
51 return false;
52
53 return true;
54}
55
58{
59 // The tfOfferCreateMask is built assuming that PermissionedDEX is
60 // enabled
61 if (ctx.rules.enabled(featurePermissionedDEX))
62 return tfOfferCreateMask;
63 // If PermissionedDEX is not enabled, add tfHybrid to the mask,
64 // indicating it is not allowed.
66}
67
70{
71 auto& tx = ctx.tx;
72 auto& j = ctx.j;
73
74 std::uint32_t const uTxFlags = tx.getFlags();
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 tesSUCCESS;
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
322 PaymentSandbox& psb,
323 PaymentSandbox& psbCancel,
324 Amounts const& takerAmount,
325 std::optional<uint256> const& domainID)
326{
327 try
328 {
329 // If the taker is unfunded before we begin crossing there's nothing
330 // to do - just return an error.
331 //
332 // We check this in preclaim, but when selling XRP charged fees can
333 // cause a user's available balance to go to 0 (by causing it to dip
334 // below the reserve) so we check this case again.
335 STAmount const inStartBalance =
336 accountFunds(psb, account_, takerAmount.in, fhZERO_IF_FROZEN, j_);
337 if (inStartBalance <= beast::zero)
338 {
339 // The account balance can't cover even part of the offer.
340 JLOG(j_.debug()) << "Not crossing: taker is unfunded.";
341 return {tecUNFUNDED_OFFER, takerAmount};
342 }
343
344 // If the gateway has a transfer rate, accommodate that. The
345 // gateway takes its cut without any special consent from the
346 // offer taker. Set sendMax to allow for the gateway's cut.
347 Rate gatewayXferRate{QUALITY_ONE};
348 STAmount sendMax = takerAmount.in;
349 if (!sendMax.native() && (account_ != sendMax.getIssuer()))
350 {
351 gatewayXferRate = transferRate(psb, sendMax.getIssuer());
352 if (gatewayXferRate.value != QUALITY_ONE)
353 {
354 sendMax = multiplyRound(
355 takerAmount.in,
356 gatewayXferRate,
357 takerAmount.in.issue(),
358 true);
359 }
360 }
361
362 // Payment flow code compares quality after the transfer rate is
363 // included. Since transfer rate is incorporated compute threshold.
364 Quality threshold{takerAmount.out, sendMax};
365
366 // If we're creating a passive offer adjust the threshold so we only
367 // cross offers that have a better quality than this one.
368 std::uint32_t const txFlags = ctx_.tx.getFlags();
369 if (txFlags & tfPassive)
370 ++threshold;
371
372 // Don't send more than our balance.
373 if (sendMax > inStartBalance)
374 sendMax = inStartBalance;
375
376 // Always invoke flow() with the default path. However if neither
377 // of the takerAmount currencies are XRP then we cross through an
378 // additional path with XRP as the intermediate between two books.
379 // This second path we have to build ourselves.
380 STPathSet paths;
381 if (!takerAmount.in.native() && !takerAmount.out.native())
382 {
383 STPath path;
384 path.emplace_back(std::nullopt, xrpCurrency(), std::nullopt);
385 paths.emplace_back(std::move(path));
386 }
387 // Special handling for the tfSell flag.
388 STAmount deliver = takerAmount.out;
389 OfferCrossing offerCrossing = OfferCrossing::yes;
390 if (txFlags & tfSell)
391 {
392 offerCrossing = OfferCrossing::sell;
393 // We are selling, so we will accept *more* than the offer
394 // specified. Since we don't know how much they might offer,
395 // we allow delivery of the largest possible amount.
396 if (deliver.native())
398 else
399 // We can't use the maximum possible currency here because
400 // there might be a gateway transfer rate to account for.
401 // Since the transfer rate cannot exceed 200%, we use 1/2
402 // maxValue for our limit.
403 deliver = STAmount{
404 takerAmount.out.issue(),
407 }
408
409 // Call the payment engine's flow() to do the actual work.
410 auto const result = flow(
411 psb,
412 deliver,
413 account_,
414 account_,
415 paths,
416 true, // default path
417 !(txFlags & tfFillOrKill), // partial payment
418 true, // owner pays transfer fee
419 offerCrossing,
420 threshold,
421 sendMax,
422 domainID,
423 j_);
424
425 // If stale offers were found remove them.
426 for (auto const& toRemove : result.removableOffers)
427 {
428 if (auto otr = psb.peek(keylet::offer(toRemove)))
429 offerDelete(psb, otr, j_);
430 if (auto otr = psbCancel.peek(keylet::offer(toRemove)))
431 offerDelete(psbCancel, otr, j_);
432 }
433
434 // Determine the size of the final offer after crossing.
435 auto afterCross = takerAmount; // If !tesSUCCESS offer unchanged
436 if (isTesSuccess(result.result()))
437 {
438 STAmount const takerInBalance = accountFunds(
439 psb, account_, takerAmount.in, fhZERO_IF_FROZEN, j_);
440
441 if (takerInBalance <= beast::zero)
442 {
443 // If offer crossing exhausted the account's funds don't
444 // create the offer.
445 afterCross.in.clear();
446 afterCross.out.clear();
447 }
448 else
449 {
450 STAmount const rate{
451 Quality{takerAmount.out, takerAmount.in}.rate()};
452
453 if (txFlags & tfSell)
454 {
455 // If selling then scale the new out amount based on how
456 // much we sold during crossing. This preserves the offer
457 // Quality,
458
459 // Reduce the offer that is placed by the crossed amount.
460 // Note that we must ignore the portion of the
461 // actualAmountIn that may have been consumed by a
462 // gateway's transfer rate.
463 STAmount nonGatewayAmountIn = result.actualAmountIn;
464 if (gatewayXferRate.value != QUALITY_ONE)
465 nonGatewayAmountIn = divideRound(
466 result.actualAmountIn,
467 gatewayXferRate,
468 takerAmount.in.issue(),
469 true);
470
471 afterCross.in -= nonGatewayAmountIn;
472
473 // It's possible that the divRound will cause our subtract
474 // to go slightly negative. So limit afterCross.in to zero.
475 if (afterCross.in < beast::zero)
476 // We should verify that the difference *is* small, but
477 // what is a good threshold to check?
478 afterCross.in.clear();
479
480 afterCross.out = [&]() {
481 // Careful analysis showed that rounding up this
482 // divRound result could lead to placing a reduced
483 // offer in the ledger that blocks order books. So
484 // the fixReducedOffersV1 amendment changes the
485 // behavior to round down instead.
486 if (psb.rules().enabled(fixReducedOffersV1))
487 return divRoundStrict(
488 afterCross.in,
489 rate,
490 takerAmount.out.issue(),
491 false);
492
493 return divRound(
494 afterCross.in, rate, takerAmount.out.issue(), true);
495 }();
496 }
497 else
498 {
499 // If not selling, we scale the input based on the
500 // remaining output. This too preserves the offer
501 // Quality.
502 afterCross.out -= result.actualAmountOut;
503 XRPL_ASSERT(
504 afterCross.out >= beast::zero,
505 "ripple::CreateOffer::flowCross : minimum offer");
506 if (afterCross.out < beast::zero)
507 afterCross.out.clear();
508 afterCross.in = mulRound(
509 afterCross.out, rate, takerAmount.in.issue(), true);
510 }
511 }
512 }
513
514 // Return how much of the offer is left.
515 return {tesSUCCESS, afterCross};
516 }
517 catch (std::exception const& e)
518 {
519 JLOG(j_.error()) << "Exception during offer crossing: " << e.what();
520 }
521 return {tecINTERNAL, takerAmount};
522}
523
526{
527 std::string txt = amount.getText();
528 txt += "/";
529 txt += to_string(amount.issue().currency);
530 return txt;
531}
532
533TER
535 Sandbox& sb,
537 Keylet const& offerKey,
538 STAmount const& saTakerPays,
539 STAmount const& saTakerGets,
540 std::function<void(SLE::ref, std::optional<uint256>)> const& setDir)
541{
542 if (!sleOffer->isFieldPresent(sfDomainID))
543 return tecINTERNAL; // LCOV_EXCL_LINE
544
545 // set hybrid flag
546 sleOffer->setFlag(lsfHybrid);
547
548 // if offer is hybrid, need to also place into open offer dir
549 Book const book{saTakerPays.issue(), saTakerGets.issue(), std::nullopt};
550
551 auto dir =
552 keylet::quality(keylet::book(book), getRate(saTakerGets, saTakerPays));
553 bool const bookExists = sb.exists(dir);
554
555 auto const bookNode = sb.dirAppend(dir, offerKey, [&](SLE::ref sle) {
556 // don't set domainID on the directory object since this directory is
557 // for open book
558 setDir(sle, std::nullopt);
559 });
560
561 if (!bookNode)
562 {
563 JLOG(j_.debug())
564 << "final result: failed to add hybrid offer to open book";
565 return tecDIR_FULL; // LCOV_EXCL_LINE
566 }
567
568 STArray bookArr(sfAdditionalBooks, 1);
569 auto bookInfo = STObject::makeInnerObject(sfBook);
570 bookInfo.setFieldH256(sfBookDirectory, dir.key);
571 bookInfo.setFieldU64(sfBookNode, *bookNode);
572 bookArr.push_back(std::move(bookInfo));
573
574 if (!bookExists)
576
577 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
578 return tesSUCCESS;
579}
580
583{
584 using beast::zero;
585
586 std::uint32_t const uTxFlags = ctx_.tx.getFlags();
587
588 bool const bPassive(uTxFlags & tfPassive);
589 bool const bImmediateOrCancel(uTxFlags & tfImmediateOrCancel);
590 bool const bFillOrKill(uTxFlags & tfFillOrKill);
591 bool const bSell(uTxFlags & tfSell);
592 bool const bHybrid(uTxFlags & tfHybrid);
593
594 auto saTakerPays = ctx_.tx[sfTakerPays];
595 auto saTakerGets = ctx_.tx[sfTakerGets];
596 auto const domainID = ctx_.tx[~sfDomainID];
597
598 auto const cancelSequence = ctx_.tx[~sfOfferSequence];
599
600 // Note that we we use the value from the sequence or ticket as the
601 // offer sequence. For more explanation see comments in SeqProxy.h.
602 auto const offerSequence = ctx_.tx.getSeqValue();
603
604 // This is the original rate of the offer, and is the rate at which
605 // it will be placed, even if crossing offers change the amounts that
606 // end up on the books.
607 auto uRate = getRate(saTakerGets, saTakerPays);
608
609 auto viewJ = ctx_.app.journal("View");
610
611 TER result = tesSUCCESS;
612
613 // Process a cancellation request that's passed along with an offer.
614 if (cancelSequence)
615 {
616 auto const sleCancel =
617 sb.peek(keylet::offer(account_, *cancelSequence));
618
619 // It's not an error to not find the offer to cancel: it might have
620 // been consumed or removed. If it is found, however, it's an error
621 // to fail to delete it.
622 if (sleCancel)
623 {
624 JLOG(j_.debug()) << "Create cancels order " << *cancelSequence;
625 result = offerDelete(sb, sleCancel, viewJ);
626 }
627 }
628
629 auto const expiration = ctx_.tx[~sfExpiration];
630
631 if (hasExpired(sb, expiration))
632 {
633 // If the offer has expired, the transaction has successfully
634 // done nothing, so short circuit from here.
635 //
636 // The return code change is attached to featureDepositPreauth as a
637 // convenience. The change is not big enough to deserve a fix code.
638 TER const ter{
639 sb.rules().enabled(featureDepositPreauth) ? TER{tecEXPIRED}
640 : TER{tesSUCCESS}};
641 return {ter, true};
642 }
643
644 bool const bOpenLedger = sb.open();
645 bool crossed = false;
646
647 if (result == tesSUCCESS)
648 {
649 // If a tick size applies, round the offer to the tick size
650 auto const& uPaysIssuerID = saTakerPays.getIssuer();
651 auto const& uGetsIssuerID = saTakerGets.getIssuer();
652
653 std::uint8_t uTickSize = Quality::maxTickSize;
654 if (!isXRP(uPaysIssuerID))
655 {
656 auto const sle = sb.read(keylet::account(uPaysIssuerID));
657 if (sle && sle->isFieldPresent(sfTickSize))
658 uTickSize = std::min(uTickSize, (*sle)[sfTickSize]);
659 }
660 if (!isXRP(uGetsIssuerID))
661 {
662 auto const sle = sb.read(keylet::account(uGetsIssuerID));
663 if (sle && sle->isFieldPresent(sfTickSize))
664 uTickSize = std::min(uTickSize, (*sle)[sfTickSize]);
665 }
666 if (uTickSize < Quality::maxTickSize)
667 {
668 auto const rate =
669 Quality{saTakerGets, saTakerPays}.round(uTickSize).rate();
670
671 // We round the side that's not exact,
672 // just as if the offer happened to execute
673 // at a slightly better (for the placer) rate
674 if (bSell)
675 {
676 // this is a sell, round taker pays
677 saTakerPays = multiply(saTakerGets, rate, saTakerPays.issue());
678 }
679 else
680 {
681 // this is a buy, round taker gets
682 saTakerGets = divide(saTakerPays, rate, saTakerGets.issue());
683 }
684 if (!saTakerGets || !saTakerPays)
685 {
686 JLOG(j_.debug()) << "Offer rounded to zero";
687 return {result, true};
688 }
689
690 uRate = getRate(saTakerGets, saTakerPays);
691 }
692
693 // We reverse pays and gets because during crossing we are taking.
694 Amounts const takerAmount(saTakerGets, saTakerPays);
695
696 JLOG(j_.debug()) << "Attempting cross: "
697 << to_string(takerAmount.in.issue()) << " -> "
698 << to_string(takerAmount.out.issue());
699
700 if (auto stream = j_.trace())
701 {
702 stream << " mode: " << (bPassive ? "passive " : "")
703 << (bSell ? "sell" : "buy");
704 stream << " in: " << format_amount(takerAmount.in);
705 stream << " out: " << format_amount(takerAmount.out);
706 }
707
708 // The amount of the offer that is unfilled after crossing has been
709 // performed. It may be equal to the original amount (didn't cross),
710 // empty (fully crossed), or something in-between.
711 Amounts place_offer;
712 PaymentSandbox psbFlow{&sb};
713 PaymentSandbox psbCancelFlow{&sbCancel};
714
715 std::tie(result, place_offer) =
716 flowCross(psbFlow, psbCancelFlow, takerAmount, domainID);
717 psbFlow.apply(sb);
718 psbCancelFlow.apply(sbCancel);
719
720 // We expect the implementation of cross to succeed
721 // or give a tec.
722 XRPL_ASSERT(
723 result == tesSUCCESS || isTecClaim(result),
724 "ripple::CreateOffer::applyGuts : result is tesSUCCESS or "
725 "tecCLAIM");
726
727 if (auto stream = j_.trace())
728 {
729 stream << "Cross result: " << transToken(result);
730 stream << " in: " << format_amount(place_offer.in);
731 stream << " out: " << format_amount(place_offer.out);
732 }
733
734 if (result == tecFAILED_PROCESSING && bOpenLedger)
735 result = telFAILED_PROCESSING;
736
737 if (result != tesSUCCESS)
738 {
739 JLOG(j_.debug()) << "final result: " << transToken(result);
740 return {result, true};
741 }
742
743 XRPL_ASSERT(
744 saTakerGets.issue() == place_offer.in.issue(),
745 "ripple::CreateOffer::applyGuts : taker gets issue match");
746 XRPL_ASSERT(
747 saTakerPays.issue() == place_offer.out.issue(),
748 "ripple::CreateOffer::applyGuts : taker pays issue match");
749
750 if (takerAmount != place_offer)
751 crossed = true;
752
753 // The offer that we need to place after offer crossing should
754 // never be negative. If it is, something went very very wrong.
755 if (place_offer.in < zero || place_offer.out < zero)
756 {
757 JLOG(j_.fatal()) << "Cross left offer negative!"
758 << " in: " << format_amount(place_offer.in)
759 << " out: " << format_amount(place_offer.out);
760 return {tefINTERNAL, true};
761 }
762
763 if (place_offer.in == zero || place_offer.out == zero)
764 {
765 JLOG(j_.debug()) << "Offer fully crossed!";
766 return {result, true};
767 }
768
769 // We now need to adjust the offer to reflect the amount left after
770 // crossing. We reverse in and out here, since during crossing we
771 // were the taker.
772 saTakerPays = place_offer.out;
773 saTakerGets = place_offer.in;
774 }
775
776 XRPL_ASSERT(
777 saTakerPays > zero && saTakerGets > zero,
778 "ripple::CreateOffer::applyGuts : taker pays and gets positive");
779
780 if (result != tesSUCCESS)
781 {
782 JLOG(j_.debug()) << "final result: " << transToken(result);
783 return {result, true};
784 }
785
786 if (auto stream = j_.trace())
787 {
788 stream << "Place" << (crossed ? " remaining " : " ") << "offer:";
789 stream << " Pays: " << saTakerPays.getFullText();
790 stream << " Gets: " << saTakerGets.getFullText();
791 }
792
793 // For 'fill or kill' offers, failure to fully cross means that the
794 // entire operation should be aborted, with only fees paid.
795 if (bFillOrKill)
796 {
797 JLOG(j_.trace()) << "Fill or Kill: offer killed";
798 return {tecKILLED, false};
799 }
800
801 // For 'immediate or cancel' offers, the amount remaining doesn't get
802 // placed - it gets canceled and the operation succeeds.
803 if (bImmediateOrCancel)
804 {
805 JLOG(j_.trace()) << "Immediate or cancel: offer canceled";
806 if (!crossed && sb.rules().enabled(featureImmediateOfferKilled))
807 // If the ImmediateOfferKilled amendment is enabled, any
808 // ImmediateOrCancel offer that transfers absolutely no funds
809 // returns tecKILLED rather than tesSUCCESS. Motivation for the
810 // change is here: https://github.com/ripple/rippled/issues/4115
811 return {tecKILLED, false};
812 return {tesSUCCESS, true};
813 }
814
815 auto const sleCreator = sb.peek(keylet::account(account_));
816 if (!sleCreator)
817 return {tefINTERNAL, false};
818
819 {
820 XRPAmount reserve =
821 sb.fees().accountReserve(sleCreator->getFieldU32(sfOwnerCount) + 1);
822
823 if (mPriorBalance < reserve)
824 {
825 // If we are here, the signing account had an insufficient reserve
826 // *prior* to our processing. If something actually crossed, then
827 // we allow this; otherwise, we just claim a fee.
828 if (!crossed)
829 result = tecINSUF_RESERVE_OFFER;
830
831 if (result != tesSUCCESS)
832 {
833 JLOG(j_.debug()) << "final result: " << transToken(result);
834 }
835
836 return {result, true};
837 }
838 }
839
840 // We need to place the remainder of the offer into its order book.
841 auto const offer_index = keylet::offer(account_, offerSequence);
842
843 // Add offer to owner's directory.
844 auto const ownerNode = sb.dirInsert(
846
847 if (!ownerNode)
848 {
849 // LCOV_EXCL_START
850 JLOG(j_.debug())
851 << "final result: failed to add offer to owner's directory";
852 return {tecDIR_FULL, true};
853 // LCOV_EXCL_STOP
854 }
855
856 // Update owner count.
857 adjustOwnerCount(sb, sleCreator, 1, viewJ);
858
859 JLOG(j_.trace()) << "adding to book: " << to_string(saTakerPays.issue())
860 << " : " << to_string(saTakerGets.issue())
861 << (domainID ? (" : " + to_string(*domainID)) : "");
862
863 Book const book{saTakerPays.issue(), saTakerGets.issue(), domainID};
864
865 // Add offer to order book, using the original rate
866 // before any crossing occured.
867 //
868 // Regular offer - BookDirectory points to open directory
869 //
870 // Domain offer (w/o hyrbid) - BookDirectory points to domain
871 // directory
872 //
873 // Hybrid domain offer - BookDirectory points to domain directory,
874 // and AdditionalBooks field stores one entry that points to the open
875 // directory
876 auto dir = keylet::quality(keylet::book(book), uRate);
877 bool const bookExisted = static_cast<bool>(sb.peek(dir));
878
879 auto setBookDir = [&](SLE::ref sle,
880 std::optional<uint256> const& maybeDomain) {
881 sle->setFieldH160(sfTakerPaysCurrency, saTakerPays.issue().currency);
882 sle->setFieldH160(sfTakerPaysIssuer, saTakerPays.issue().account);
883 sle->setFieldH160(sfTakerGetsCurrency, saTakerGets.issue().currency);
884 sle->setFieldH160(sfTakerGetsIssuer, saTakerGets.issue().account);
885 sle->setFieldU64(sfExchangeRate, uRate);
886 if (maybeDomain)
887 sle->setFieldH256(sfDomainID, *maybeDomain);
888 };
889
890 auto const bookNode = sb.dirAppend(dir, offer_index, [&](SLE::ref sle) {
891 // sets domainID on book directory if it's a domain offer
892 setBookDir(sle, domainID);
893 });
894
895 if (!bookNode)
896 {
897 // LCOV_EXCL_START
898 JLOG(j_.debug()) << "final result: failed to add offer to book";
899 return {tecDIR_FULL, true};
900 // LCOV_EXCL_STOP
901 }
902
903 auto sleOffer = std::make_shared<SLE>(offer_index);
904 sleOffer->setAccountID(sfAccount, account_);
905 sleOffer->setFieldU32(sfSequence, offerSequence);
906 sleOffer->setFieldH256(sfBookDirectory, dir.key);
907 sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
908 sleOffer->setFieldAmount(sfTakerGets, saTakerGets);
909 sleOffer->setFieldU64(sfOwnerNode, *ownerNode);
910 sleOffer->setFieldU64(sfBookNode, *bookNode);
911 if (expiration)
912 sleOffer->setFieldU32(sfExpiration, *expiration);
913 if (bPassive)
914 sleOffer->setFlag(lsfPassive);
915 if (bSell)
916 sleOffer->setFlag(lsfSell);
917 if (domainID)
918 sleOffer->setFieldH256(sfDomainID, *domainID);
919
920 // if it's a hybrid offer, set hybrid flag, and create an open dir
921 if (bHybrid)
922 {
923 auto const res = applyHybrid(
924 sb, sleOffer, offer_index, saTakerPays, saTakerGets, setBookDir);
925 if (res != tesSUCCESS)
926 return {res, true}; // LCOV_EXCL_LINE
927 }
928
929 sb.insert(sleOffer);
930
931 if (!bookExisted)
933
934 JLOG(j_.debug()) << "final result: success";
935
936 return {tesSUCCESS, true};
937}
938
939TER
941{
942 // This is the ledger view that we work against. Transactions are applied
943 // as we go on processing transactions.
944 Sandbox sb(&ctx_.view());
945
946 // This is a ledger with just the fees paid and any unfunded or expired
947 // offers we encounter removed. It's used when handling Fill-or-Kill offers,
948 // if the order isn't going to be placed, to avoid wasting the work we did.
949 Sandbox sbCancel(&ctx_.view());
950
951 auto const result = applyGuts(sb, sbCancel);
952 if (result.second)
953 sb.apply(ctx_.rawView());
954 else
955 sbCancel.apply(ctx_.rawView());
956 return result.first;
957}
958
959} // namespace ripple
A generic endpoint for log messages.
Definition Journal.h:60
Stream fatal() const
Definition Journal.h:352
Stream error() const
Definition Journal.h:346
Stream debug() const
Definition Journal.h:328
Stream trace() const
Severity stream access functions.
Definition Journal.h:322
virtual beast::Journal journal(std::string const &name)=0
virtual OrderBookDB & getOrderBookDB()=0
ApplyView & view()
Application & app
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:280
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:319
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)
static TER checkAcceptAsset(ReadView const &view, ApplyFlags const flags, AccountID const id, beast::Journal const j, Issue const &issue)
static TER preclaim(PreclaimContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
static std::string format_amount(STAmount const &amount)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
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)
TER doApply() override
Precondition: fee collection is likely.
static bool checkExtraFeatures(PreflightContext const &ctx)
std::pair< TER, bool > applyGuts(Sandbox &view, Sandbox &view_cancel)
A currency issued by an account.
Definition Issue.h:33
AccountID account
Definition Issue.h:36
Currency currency
Definition Issue.h:35
void addOrderBook(Book const &)
A wrapper which makes credits unavailable to balances.
A view into a ledger.
Definition ReadView.h:51
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:130
static int const cMaxOffset
Definition STAmount.h:66
Currency const & getCurrency() const
Definition STAmount.h:502
static std::uint64_t const cMaxValue
Definition STAmount.h:70
std::string getText() const override
Definition STAmount.cpp:706
AccountID const & getIssuer() const
Definition STAmount.h:508
Issue const & issue() const
Definition STAmount.h:496
static std::uint64_t const cMaxNative
Definition STAmount.h:71
std::string getFullText() const override
Definition STAmount.cpp:696
bool native() const noexcept
Definition STAmount.h:458
void push_back(STObject const &object)
Definition STArray.h:212
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
AccountID const account_
Definition Transactor.h:147
ApplyView & view()
Definition Transactor.h:163
beast::Journal const j_
Definition Transactor.h:145
XRPAmount mPriorBalance
Definition Transactor.h:148
ApplyContext & ctx_
Definition Transactor.h:143
Class describing the consequences to the account of applying a transaction if the transaction consume...
Definition applySteps.h:58
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 is_same_v
T min(T... args)
Keylet quality(Keylet const &k, std::uint64_t q) noexcept
The initial directory page for a specific quality.
Definition Indexes.cpp:280
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:244
static book_t const book
Definition Indexes.h:105
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:374
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition Indexes.cpp:274
bool accountInDomain(ReadView const &view, AccountID const &account, Domain const &domainID)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
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...
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:554
@ fhZERO_IF_FROZEN
Definition View.h:77
bool isXRP(AccountID const &c)
Definition AccountID.h:90
@ telFAILED_PROCESSING
Definition TER.h:56
constexpr std::uint32_t tfOfferCreateMask
Definition TxFlags.h:103
STAmount divRoundStrict(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
bool isLegalNet(STAmount const &value)
Definition STAmount.h:600
@ lsfHighDeepFreeze
@ lsfLowDeepFreeze
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1032
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:1050
constexpr std::uint32_t tfFillOrKill
Definition TxFlags.h:100
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
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
std::string transToken(TER code)
Definition TER.cpp:264
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:173
Currency const & xrpCurrency()
XRP currency.
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:762
@ 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)
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)
STAmount multiplyRound(STAmount const &amount, Rate const &rate, bool roundUp)
Definition Rate2.cpp:64
@ tapRETRY
Definition ApplyView.h:39
constexpr std::uint32_t tfSell
Definition TxFlags.h:101
@ 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:681
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1647
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Definition View.cpp:182
@ 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
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
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:80
ReadView const & view
Definition Transactor.h:83
beast::Journal const j
Definition Transactor.h:88
State information when preflighting a tx.
Definition Transactor.h:35
beast::Journal const j
Definition Transactor.h:42
Represents a transfer rate.
Definition Rate.h:40
T tie(T... args)
T what(T... args)