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
48{
49 if (ctx.tx.isFieldPresent(sfDomainID) &&
50 !ctx.rules.enabled(featurePermissionedDEX))
51 return temDISABLED;
52
53 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
54 return ret;
55
56 auto& tx = ctx.tx;
57 auto& j = ctx.j;
58
59 std::uint32_t const uTxFlags = tx.getFlags();
60
61 if (uTxFlags & tfOfferCreateMask)
62 {
63 JLOG(j.debug()) << "Malformed transaction: Invalid flags set.";
64 return temINVALID_FLAG;
65 }
66
67 if (!ctx.rules.enabled(featurePermissionedDEX) && tx.isFlag(tfHybrid))
68 return temINVALID_FLAG;
69
70 if (tx.isFlag(tfHybrid) && !tx.isFieldPresent(sfDomainID))
71 return temINVALID_FLAG;
72
73 bool const bImmediateOrCancel(uTxFlags & tfImmediateOrCancel);
74 bool const bFillOrKill(uTxFlags & tfFillOrKill);
75
76 if (bImmediateOrCancel && bFillOrKill)
77 {
78 JLOG(j.debug()) << "Malformed transaction: both IoC and FoK set.";
79 return temINVALID_FLAG;
80 }
81
82 bool const bHaveExpiration(tx.isFieldPresent(sfExpiration));
83
84 if (bHaveExpiration && (tx.getFieldU32(sfExpiration) == 0))
85 {
86 JLOG(j.debug()) << "Malformed offer: bad expiration";
87 return temBAD_EXPIRATION;
88 }
89
90 if (auto const cancelSequence = tx[~sfOfferSequence];
91 cancelSequence && *cancelSequence == 0)
92 {
93 JLOG(j.debug()) << "Malformed offer: bad cancel sequence";
94 return temBAD_SEQUENCE;
95 }
96
97 STAmount saTakerPays = tx[sfTakerPays];
98 STAmount saTakerGets = tx[sfTakerGets];
99
100 if (!isLegalNet(saTakerPays) || !isLegalNet(saTakerGets))
101 return temBAD_AMOUNT;
102
103 if (saTakerPays.native() && saTakerGets.native())
104 {
105 JLOG(j.debug()) << "Malformed offer: redundant (XRP for XRP)";
106 return temBAD_OFFER;
107 }
108 if (saTakerPays <= beast::zero || saTakerGets <= beast::zero)
109 {
110 JLOG(j.debug()) << "Malformed offer: bad amount";
111 return temBAD_OFFER;
112 }
113
114 auto const& uPaysIssuerID = saTakerPays.getIssuer();
115 auto const& uPaysCurrency = saTakerPays.getCurrency();
116
117 auto const& uGetsIssuerID = saTakerGets.getIssuer();
118 auto const& uGetsCurrency = saTakerGets.getCurrency();
119
120 if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID)
121 {
122 JLOG(j.debug()) << "Malformed offer: redundant (IOU for IOU)";
123 return temREDUNDANT;
124 }
125 // We don't allow a non-native currency to use the currency code XRP.
126 if (badCurrency() == uPaysCurrency || badCurrency() == uGetsCurrency)
127 {
128 JLOG(j.debug()) << "Malformed offer: bad currency";
129 return temBAD_CURRENCY;
130 }
131
132 if (saTakerPays.native() != !uPaysIssuerID ||
133 saTakerGets.native() != !uGetsIssuerID)
134 {
135 JLOG(j.debug()) << "Malformed offer: bad issuer";
136 return temBAD_ISSUER;
137 }
138
139 return preflight2(ctx);
140}
141
142TER
144{
145 auto const id = ctx.tx[sfAccount];
146
147 auto saTakerPays = ctx.tx[sfTakerPays];
148 auto saTakerGets = ctx.tx[sfTakerGets];
149
150 auto const& uPaysIssuerID = saTakerPays.getIssuer();
151 auto const& uPaysCurrency = saTakerPays.getCurrency();
152
153 auto const& uGetsIssuerID = saTakerGets.getIssuer();
154
155 auto const cancelSequence = ctx.tx[~sfOfferSequence];
156
157 auto const sleCreator = ctx.view.read(keylet::account(id));
158 if (!sleCreator)
159 return terNO_ACCOUNT;
160
161 std::uint32_t const uAccountSequence = sleCreator->getFieldU32(sfSequence);
162
163 auto viewJ = ctx.app.journal("View");
164
165 if (isGlobalFrozen(ctx.view, uPaysIssuerID) ||
166 isGlobalFrozen(ctx.view, uGetsIssuerID))
167 {
168 JLOG(ctx.j.debug()) << "Offer involves frozen asset";
169 return tecFROZEN;
170 }
171
172 if (accountFunds(ctx.view, id, saTakerGets, fhZERO_IF_FROZEN, viewJ) <=
173 beast::zero)
174 {
175 JLOG(ctx.j.debug())
176 << "delay: Offers must be at least partially funded.";
177 return tecUNFUNDED_OFFER;
178 }
179
180 // This can probably be simplified to make sure that you cancel sequences
181 // before the transaction sequence number.
182 if (cancelSequence && (uAccountSequence <= *cancelSequence))
183 {
184 JLOG(ctx.j.debug()) << "uAccountSequenceNext=" << uAccountSequence
185 << " uOfferSequence=" << *cancelSequence;
186 return temBAD_SEQUENCE;
187 }
188
189 if (hasExpired(ctx.view, ctx.tx[~sfExpiration]))
190 {
191 // Note that this will get checked again in applyGuts, but it saves
192 // us a call to checkAcceptAsset and possible false negative.
193 //
194 // The return code change is attached to featureDepositPreauth as a
195 // convenience, as the change is not big enough to deserve its own
196 // amendment.
197 return ctx.view.rules().enabled(featureDepositPreauth)
198 ? TER{tecEXPIRED}
199 : TER{tesSUCCESS};
200 }
201
202 // Make sure that we are authorized to hold what the taker will pay us.
203 if (!saTakerPays.native())
204 {
205 auto result = checkAcceptAsset(
206 ctx.view,
207 ctx.flags,
208 id,
209 ctx.j,
210 Issue(uPaysCurrency, uPaysIssuerID));
211 if (result != tesSUCCESS)
212 return result;
213 }
214
215 // if domain is specified, make sure that domain exists and the offer create
216 // is part of the domain
217 if (ctx.tx.isFieldPresent(sfDomainID))
218 {
220 ctx.view, id, ctx.tx[sfDomainID]))
221 return tecNO_PERMISSION;
222 }
223
224 return tesSUCCESS;
225}
226
227TER
229 ReadView const& view,
230 ApplyFlags const flags,
231 AccountID const id,
232 beast::Journal const j,
233 Issue const& issue)
234{
235 // Only valid for custom currencies
236 XRPL_ASSERT(
237 !isXRP(issue.currency),
238 "ripple::CreateOffer::checkAcceptAsset : input is not XRP");
239
240 auto const issuerAccount = view.read(keylet::account(issue.account));
241
242 if (!issuerAccount)
243 {
244 JLOG(j.debug())
245 << "delay: can't receive IOUs from non-existent issuer: "
246 << to_string(issue.account);
247
248 return (flags & tapRETRY) ? TER{terNO_ACCOUNT} : TER{tecNO_ISSUER};
249 }
250
251 // This code is attached to the DepositPreauth amendment as a matter of
252 // convenience. The change is not significant enough to deserve its
253 // own amendment.
254 if (view.rules().enabled(featureDepositPreauth) && (issue.account == id))
255 // An account can always accept its own issuance.
256 return tesSUCCESS;
257
258 if ((*issuerAccount)[sfFlags] & lsfRequireAuth)
259 {
260 auto const trustLine =
261 view.read(keylet::line(id, issue.account, issue.currency));
262
263 if (!trustLine)
264 {
265 return (flags & tapRETRY) ? TER{terNO_LINE} : TER{tecNO_LINE};
266 }
267
268 // Entries have a canonical representation, determined by a
269 // lexicographical "greater than" comparison employing strict weak
270 // ordering. Determine which entry we need to access.
271 bool const canonical_gt(id > issue.account);
272
273 bool const is_authorized(
274 (*trustLine)[sfFlags] & (canonical_gt ? lsfLowAuth : lsfHighAuth));
275
276 if (!is_authorized)
277 {
278 JLOG(j.debug())
279 << "delay: can't receive IOUs from issuer without auth.";
280
281 return (flags & tapRETRY) ? TER{terNO_AUTH} : TER{tecNO_AUTH};
282 }
283 }
284
285 // An account can not create a trustline to itself, so no line can exist
286 // to be frozen. Additionally, an issuer can always accept its own
287 // issuance.
288 if (issue.account == id)
289 {
290 return tesSUCCESS;
291 }
292
293 auto const trustLine =
294 view.read(keylet::line(id, issue.account, issue.currency));
295
296 if (!trustLine)
297 {
298 return tesSUCCESS;
299 }
300
301 // There's no difference which side enacted deep freeze, accepting
302 // tokens shouldn't be possible.
303 bool const deepFrozen =
304 (*trustLine)[sfFlags] & (lsfLowDeepFreeze | lsfHighDeepFreeze);
305
306 if (deepFrozen)
307 {
308 return tecFROZEN;
309 }
310
311 return tesSUCCESS;
312}
313
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())
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(),
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 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
520{
521 std::string txt = amount.getText();
522 txt += "/";
523 txt += to_string(amount.issue().currency);
524 return txt;
525}
526
527TER
529 Sandbox& sb,
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)
570
571 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
572 return tesSUCCESS;
573}
574
577{
578 using beast::zero;
579
580 std::uint32_t const uTxFlags = ctx_.tx.getFlags();
581
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 // 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 // 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 (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 // 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 {
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(
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 sb.insert(sleOffer);
922
923 if (!bookExisted)
925
926 JLOG(j_.debug()) << "final result: success";
927
928 return {tesSUCCESS, true};
929}
930
931TER
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
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:317
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 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.
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:145
ApplyView & view()
Definition Transactor.h:161
beast::Journal const j_
Definition Transactor.h:143
XRPAmount mPriorBalance
Definition Transactor.h:146
ApplyContext & ctx_
Definition Transactor.h:141
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:551
@ 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:1029
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:1047
constexpr std::uint32_t tfFillOrKill
Definition TxFlags.h:100
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
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
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
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:759
@ 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:680
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1634
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
@ temDISABLED
Definition TER.h:114
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)