rippled
Loading...
Searching...
No Matches
CreateOffer.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/ledger/OrderBookDB.h>
21#include <xrpld/app/misc/PermissionedDEXHelpers.h>
22#include <xrpld/app/paths/Flow.h>
23#include <xrpld/app/tx/detail/CreateOffer.h>
24#include <xrpld/ledger/PaymentSandbox.h>
25
26#include <xrpl/basics/base_uint.h>
27#include <xrpl/beast/utility/WrappedSink.h>
28#include <xrpl/protocol/Feature.h>
29#include <xrpl/protocol/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
RawView & rawView()
Definition: ApplyContext.h:91
ApplyView & view()
Definition: ApplyContext.h:78
Application & app
Definition: ApplyContext.h:71
std::optional< std::uint64_t > dirAppend(Keylet const &directory, Keylet const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Append an entry to a directory.
Definition: ApplyView.h:281
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition: ApplyView.h:318
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.
Definition: CreateOffer.cpp:47
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
Definition: CreateOffer.cpp:36
TER applyHybrid(Sandbox &sb, std::shared_ptr< STLedgerEntry > sleOffer, Keylet const &offer_index, STAmount const &saTakerPays, STAmount const &saTakerGets, std::function< void(SLE::ref, std::optional< uint256 >)> const &setDir)
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:52
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
static int const cMaxOffset
Definition: STAmount.h:66
Currency const & getCurrency() const
Definition: STAmount.h:502
static std::uint64_t const cMaxValue
Definition: STAmount.h:70
std::string getText() const override
Definition: STAmount.cpp:706
AccountID const & getIssuer() const
Definition: STAmount.h:508
Issue const & issue() const
Definition: STAmount.h:496
static std::uint64_t const cMaxNative
Definition: STAmount.h:71
std::string getFullText() const override
Definition: STAmount.cpp:696
bool native() const noexcept
Definition: STAmount.h:458
void push_back(STObject const &object)
Definition: STArray.h:212
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:143
ApplyView & view()
Definition: Transactor.h:159
beast::Journal const j_
Definition: Transactor.h:141
XRPAmount mPriorBalance
Definition: Transactor.h:144
ApplyContext & ctx_
Definition: Transactor.h:140
Class describing the consequences to the account of applying a transaction if the transaction consume...
Definition: applySteps.h:59
Fees const & fees() const override
Returns the fees for the base ledger.
bool open() const override
Returns true if this reflects an open ledger.
void insert(std::shared_ptr< SLE > const &sle) override
Insert a new state SLE.
bool exists(Keylet const &k) const override
Determine if a state item exists.
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
Rules const & rules() const override
Returns the tx processing rules.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
T 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...
Definition: UintTypes.cpp:133
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition: View.cpp:553
@ fhZERO_IF_FROZEN
Definition: View.h:78
bool isXRP(AccountID const &c)
Definition: AccountID.h:90
@ telFAILED_PROCESSING
Definition: TER.h:56
constexpr std::uint32_t tfOfferCreateMask
Definition: TxFlags.h:103
STAmount divRoundStrict(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
Definition: STAmount.cpp:1761
bool isLegalNet(STAmount const &value)
Definition: STAmount.h:600
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition: View.cpp:761
@ lsfHighDeepFreeze
@ lsfRequireAuth
@ lsfLowDeepFreeze
constexpr std::uint32_t tfHybrid
Definition: TxFlags.h:102
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:53
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition: View.cpp:1049
constexpr std::uint32_t tfFillOrKill
Definition: TxFlags.h:100
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:91
STAmount divideRound(STAmount const &amount, Rate const &rate, bool roundUp)
Definition: Rate2.cpp:104
StrandResult< TInAmt, TOutAmt > flow(PaymentSandbox const &baseView, Strand const &strand, std::optional< TInAmt > const &maxIn, TOutAmt const &out, beast::Journal j)
Request out amount from a strand.
Definition: StrandFlow.h:105
constexpr std::uint32_t tfPassive
Definition: TxFlags.h:98
constexpr std::uint32_t tfImmediateOrCancel
Definition: TxFlags.h:99
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition: View.cpp:1471
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition: STAmount.cpp:486
@ tefINTERNAL
Definition: TER.h:173
OfferCrossing
Definition: Steps.h:45
@ yes
Definition: Steps.h:45
@ sell
Definition: Steps.h:45
static bool adjustOwnerCount(ApplyContext &ctx, int count)
Definition: SetOracle.cpp:186
std::string transToken(TER code)
Definition: TER.cpp:264
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:160
Currency const & xrpCurrency()
XRP currency.
Definition: UintTypes.cpp:119
@ tecINSUF_RESERVE_OFFER
Definition: TER.h:289
@ tecNO_ISSUER
Definition: TER.h:299
@ tecDIR_FULL
Definition: TER.h:287
@ tecUNFUNDED_OFFER
Definition: TER.h:284
@ tecFROZEN
Definition: TER.h:303
@ tecKILLED
Definition: TER.h:316
@ tecINTERNAL
Definition: TER.h:310
@ tecNO_PERMISSION
Definition: TER.h:305
@ tecNO_LINE
Definition: TER.h:301
@ tecFAILED_PROCESSING
Definition: TER.h:286
@ tecEXPIRED
Definition: TER.h:314
@ tecNO_AUTH
Definition: TER.h:300
@ tesSUCCESS
Definition: TER.h:244
bool isTesSuccess(TER x) noexcept
Definition: TER.h:674
STAmount divRound(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
Definition: STAmount.cpp:1751
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
STAmount mulRound(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
Definition: STAmount.cpp:1644
STAmount multiplyRound(STAmount const &amount, Rate const &rate, bool roundUp)
Definition: Rate2.cpp:64
ApplyFlags
Definition: ApplyView.h:31
@ tapRETRY
Definition: ApplyView.h:40
constexpr std::uint32_t tfSell
Definition: TxFlags.h:101
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition: View.cpp:175
@ terNO_ACCOUNT
Definition: TER.h:217
@ terNO_AUTH
Definition: TER.h:218
@ terNO_LINE
Definition: TER.h:219
bool isTecClaim(TER x) noexcept
Definition: TER.h:680
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Definition: View.cpp:184
@ temBAD_ISSUER
Definition: TER.h:93
@ temBAD_AMOUNT
Definition: TER.h:89
@ temREDUNDANT
Definition: TER.h:112
@ temBAD_CURRENCY
Definition: TER.h:90
@ temBAD_SEQUENCE
Definition: TER.h:104
@ temBAD_EXPIRATION
Definition: TER.h:91
@ temBAD_OFFER
Definition: TER.h:95
@ temINVALID_FLAG
Definition: TER.h:111
@ temDISABLED
Definition: TER.h:114
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
Definition: protocol/Fees.h:49
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:39
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:79
ReadView const & view
Definition: Transactor.h:82
Application & app
Definition: Transactor.h:81
beast::Journal const j
Definition: Transactor.h:87
State information when preflighting a tx.
Definition: Transactor.h:34
beast::Journal const j
Definition: Transactor.h:41
Represents a transfer rate.
Definition: Rate.h:40
T tie(T... args)
T what(T... args)