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 = divRoundStrict(
481 afterCross.in, rate, takerAmount.out.issue(), false);
482 }
483 else
484 {
485 // If not selling, we scale the input based on the
486 // remaining output. This too preserves the offer
487 // Quality.
488 afterCross.out -= result.actualAmountOut;
489 XRPL_ASSERT(
490 afterCross.out >= beast::zero,
491 "ripple::CreateOffer::flowCross : minimum offer");
492 if (afterCross.out < beast::zero)
493 afterCross.out.clear();
494 afterCross.in = mulRound(
495 afterCross.out, rate, takerAmount.in.issue(), true);
496 }
497 }
498 }
499
500 // Return how much of the offer is left.
501 return {tesSUCCESS, afterCross};
502 }
503 catch (std::exception const& e)
504 {
505 JLOG(j_.error()) << "Exception during offer crossing: " << e.what();
506 }
507 return {tecINTERNAL, takerAmount};
508}
509
512{
513 std::string txt = amount.getText();
514 txt += "/";
515 txt += to_string(amount.issue().currency);
516 return txt;
517}
518
519TER
521 Sandbox& sb,
523 Keylet const& offerKey,
524 STAmount const& saTakerPays,
525 STAmount const& saTakerGets,
526 std::function<void(SLE::ref, std::optional<uint256>)> const& setDir)
527{
528 if (!sleOffer->isFieldPresent(sfDomainID))
529 return tecINTERNAL; // LCOV_EXCL_LINE
530
531 // set hybrid flag
532 sleOffer->setFlag(lsfHybrid);
533
534 // if offer is hybrid, need to also place into open offer dir
535 Book const book{saTakerPays.issue(), saTakerGets.issue(), std::nullopt};
536
537 auto dir =
538 keylet::quality(keylet::book(book), getRate(saTakerGets, saTakerPays));
539 bool const bookExists = sb.exists(dir);
540
541 auto const bookNode = sb.dirAppend(dir, offerKey, [&](SLE::ref sle) {
542 // don't set domainID on the directory object since this directory is
543 // for open book
544 setDir(sle, std::nullopt);
545 });
546
547 if (!bookNode)
548 {
549 JLOG(j_.debug())
550 << "final result: failed to add hybrid offer to open book";
551 return tecDIR_FULL; // LCOV_EXCL_LINE
552 }
553
554 STArray bookArr(sfAdditionalBooks, 1);
555 auto bookInfo = STObject::makeInnerObject(sfBook);
556 bookInfo.setFieldH256(sfBookDirectory, dir.key);
557 bookInfo.setFieldU64(sfBookNode, *bookNode);
558 bookArr.push_back(std::move(bookInfo));
559
560 if (!bookExists)
562
563 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
564 return tesSUCCESS;
565}
566
569{
570 using beast::zero;
571
572 std::uint32_t const uTxFlags = ctx_.tx.getFlags();
573
574 bool const bPassive(uTxFlags & tfPassive);
575 bool const bImmediateOrCancel(uTxFlags & tfImmediateOrCancel);
576 bool const bFillOrKill(uTxFlags & tfFillOrKill);
577 bool const bSell(uTxFlags & tfSell);
578 bool const bHybrid(uTxFlags & tfHybrid);
579
580 auto saTakerPays = ctx_.tx[sfTakerPays];
581 auto saTakerGets = ctx_.tx[sfTakerGets];
582 auto const domainID = ctx_.tx[~sfDomainID];
583
584 auto const cancelSequence = ctx_.tx[~sfOfferSequence];
585
586 // Note that we we use the value from the sequence or ticket as the
587 // offer sequence. For more explanation see comments in SeqProxy.h.
588 auto const offerSequence = ctx_.tx.getSeqValue();
589
590 // This is the original rate of the offer, and is the rate at which
591 // it will be placed, even if crossing offers change the amounts that
592 // end up on the books.
593 auto uRate = getRate(saTakerGets, saTakerPays);
594
595 auto viewJ = ctx_.app.journal("View");
596
597 TER result = tesSUCCESS;
598
599 // Process a cancellation request that's passed along with an offer.
600 if (cancelSequence)
601 {
602 auto const sleCancel =
603 sb.peek(keylet::offer(account_, *cancelSequence));
604
605 // It's not an error to not find the offer to cancel: it might have
606 // been consumed or removed. If it is found, however, it's an error
607 // to fail to delete it.
608 if (sleCancel)
609 {
610 JLOG(j_.debug()) << "Create cancels order " << *cancelSequence;
611 result = offerDelete(sb, sleCancel, viewJ);
612 }
613 }
614
615 auto const expiration = ctx_.tx[~sfExpiration];
616
617 if (hasExpired(sb, expiration))
618 {
619 // If the offer has expired, the transaction has successfully
620 // done nothing, so short circuit from here.
621 //
622 // The return code change is attached to featureDepositPreauth as a
623 // convenience. The change is not big enough to deserve a fix code.
624 TER const ter{
625 sb.rules().enabled(featureDepositPreauth) ? TER{tecEXPIRED}
626 : TER{tesSUCCESS}};
627 return {ter, true};
628 }
629
630 bool const bOpenLedger = sb.open();
631 bool crossed = false;
632
633 if (result == tesSUCCESS)
634 {
635 // If a tick size applies, round the offer to the tick size
636 auto const& uPaysIssuerID = saTakerPays.getIssuer();
637 auto const& uGetsIssuerID = saTakerGets.getIssuer();
638
639 std::uint8_t uTickSize = Quality::maxTickSize;
640 if (!isXRP(uPaysIssuerID))
641 {
642 auto const sle = sb.read(keylet::account(uPaysIssuerID));
643 if (sle && sle->isFieldPresent(sfTickSize))
644 uTickSize = std::min(uTickSize, (*sle)[sfTickSize]);
645 }
646 if (!isXRP(uGetsIssuerID))
647 {
648 auto const sle = sb.read(keylet::account(uGetsIssuerID));
649 if (sle && sle->isFieldPresent(sfTickSize))
650 uTickSize = std::min(uTickSize, (*sle)[sfTickSize]);
651 }
652 if (uTickSize < Quality::maxTickSize)
653 {
654 auto const rate =
655 Quality{saTakerGets, saTakerPays}.round(uTickSize).rate();
656
657 // We round the side that's not exact,
658 // just as if the offer happened to execute
659 // at a slightly better (for the placer) rate
660 if (bSell)
661 {
662 // this is a sell, round taker pays
663 saTakerPays = multiply(saTakerGets, rate, saTakerPays.issue());
664 }
665 else
666 {
667 // this is a buy, round taker gets
668 saTakerGets = divide(saTakerPays, rate, saTakerGets.issue());
669 }
670 if (!saTakerGets || !saTakerPays)
671 {
672 JLOG(j_.debug()) << "Offer rounded to zero";
673 return {result, true};
674 }
675
676 uRate = getRate(saTakerGets, saTakerPays);
677 }
678
679 // We reverse pays and gets because during crossing we are taking.
680 Amounts const takerAmount(saTakerGets, saTakerPays);
681
682 JLOG(j_.debug()) << "Attempting cross: "
683 << to_string(takerAmount.in.issue()) << " -> "
684 << to_string(takerAmount.out.issue());
685
686 if (auto stream = j_.trace())
687 {
688 stream << " mode: " << (bPassive ? "passive " : "")
689 << (bSell ? "sell" : "buy");
690 stream << " in: " << format_amount(takerAmount.in);
691 stream << " out: " << format_amount(takerAmount.out);
692 }
693
694 // The amount of the offer that is unfilled after crossing has been
695 // performed. It may be equal to the original amount (didn't cross),
696 // empty (fully crossed), or something in-between.
697 Amounts place_offer;
698 PaymentSandbox psbFlow{&sb};
699 PaymentSandbox psbCancelFlow{&sbCancel};
700
701 std::tie(result, place_offer) =
702 flowCross(psbFlow, psbCancelFlow, takerAmount, domainID);
703 psbFlow.apply(sb);
704 psbCancelFlow.apply(sbCancel);
705
706 // We expect the implementation of cross to succeed
707 // or give a tec.
708 XRPL_ASSERT(
709 result == tesSUCCESS || isTecClaim(result),
710 "ripple::CreateOffer::applyGuts : result is tesSUCCESS or "
711 "tecCLAIM");
712
713 if (auto stream = j_.trace())
714 {
715 stream << "Cross result: " << transToken(result);
716 stream << " in: " << format_amount(place_offer.in);
717 stream << " out: " << format_amount(place_offer.out);
718 }
719
720 if (result == tecFAILED_PROCESSING && bOpenLedger)
721 result = telFAILED_PROCESSING;
722
723 if (result != tesSUCCESS)
724 {
725 JLOG(j_.debug()) << "final result: " << transToken(result);
726 return {result, true};
727 }
728
729 XRPL_ASSERT(
730 saTakerGets.issue() == place_offer.in.issue(),
731 "ripple::CreateOffer::applyGuts : taker gets issue match");
732 XRPL_ASSERT(
733 saTakerPays.issue() == place_offer.out.issue(),
734 "ripple::CreateOffer::applyGuts : taker pays issue match");
735
736 if (takerAmount != place_offer)
737 crossed = true;
738
739 // The offer that we need to place after offer crossing should
740 // never be negative. If it is, something went very very wrong.
741 if (place_offer.in < zero || place_offer.out < zero)
742 {
743 JLOG(j_.fatal()) << "Cross left offer negative!"
744 << " in: " << format_amount(place_offer.in)
745 << " out: " << format_amount(place_offer.out);
746 return {tefINTERNAL, true};
747 }
748
749 if (place_offer.in == zero || place_offer.out == zero)
750 {
751 JLOG(j_.debug()) << "Offer fully crossed!";
752 return {result, true};
753 }
754
755 // We now need to adjust the offer to reflect the amount left after
756 // crossing. We reverse in and out here, since during crossing we
757 // were the taker.
758 saTakerPays = place_offer.out;
759 saTakerGets = place_offer.in;
760 }
761
762 XRPL_ASSERT(
763 saTakerPays > zero && saTakerGets > zero,
764 "ripple::CreateOffer::applyGuts : taker pays and gets positive");
765
766 if (result != tesSUCCESS)
767 {
768 JLOG(j_.debug()) << "final result: " << transToken(result);
769 return {result, true};
770 }
771
772 if (auto stream = j_.trace())
773 {
774 stream << "Place" << (crossed ? " remaining " : " ") << "offer:";
775 stream << " Pays: " << saTakerPays.getFullText();
776 stream << " Gets: " << saTakerGets.getFullText();
777 }
778
779 // For 'fill or kill' offers, failure to fully cross means that the
780 // entire operation should be aborted, with only fees paid.
781 if (bFillOrKill)
782 {
783 JLOG(j_.trace()) << "Fill or Kill: offer killed";
784 return {tecKILLED, false};
785 }
786
787 // For 'immediate or cancel' offers, the amount remaining doesn't get
788 // placed - it gets canceled and the operation succeeds.
789 if (bImmediateOrCancel)
790 {
791 JLOG(j_.trace()) << "Immediate or cancel: offer canceled";
792 if (!crossed && sb.rules().enabled(featureImmediateOfferKilled))
793 // If the ImmediateOfferKilled amendment is enabled, any
794 // ImmediateOrCancel offer that transfers absolutely no funds
795 // returns tecKILLED rather than tesSUCCESS. Motivation for the
796 // change is here: https://github.com/ripple/rippled/issues/4115
797 return {tecKILLED, false};
798 return {tesSUCCESS, true};
799 }
800
801 auto const sleCreator = sb.peek(keylet::account(account_));
802 if (!sleCreator)
803 return {tefINTERNAL, false};
804
805 {
806 XRPAmount reserve =
807 sb.fees().accountReserve(sleCreator->getFieldU32(sfOwnerCount) + 1);
808
809 if (mPriorBalance < reserve)
810 {
811 // If we are here, the signing account had an insufficient reserve
812 // *prior* to our processing. If something actually crossed, then
813 // we allow this; otherwise, we just claim a fee.
814 if (!crossed)
815 result = tecINSUF_RESERVE_OFFER;
816
817 if (result != tesSUCCESS)
818 {
819 JLOG(j_.debug()) << "final result: " << transToken(result);
820 }
821
822 return {result, true};
823 }
824 }
825
826 // We need to place the remainder of the offer into its order book.
827 auto const offer_index = keylet::offer(account_, offerSequence);
828
829 // Add offer to owner's directory.
830 auto const ownerNode = sb.dirInsert(
832
833 if (!ownerNode)
834 {
835 // LCOV_EXCL_START
836 JLOG(j_.debug())
837 << "final result: failed to add offer to owner's directory";
838 return {tecDIR_FULL, true};
839 // LCOV_EXCL_STOP
840 }
841
842 // Update owner count.
843 adjustOwnerCount(sb, sleCreator, 1, viewJ);
844
845 JLOG(j_.trace()) << "adding to book: " << to_string(saTakerPays.issue())
846 << " : " << to_string(saTakerGets.issue())
847 << (domainID ? (" : " + to_string(*domainID)) : "");
848
849 Book const book{saTakerPays.issue(), saTakerGets.issue(), domainID};
850
851 // Add offer to order book, using the original rate
852 // before any crossing occured.
853 //
854 // Regular offer - BookDirectory points to open directory
855 //
856 // Domain offer (w/o hyrbid) - BookDirectory points to domain
857 // directory
858 //
859 // Hybrid domain offer - BookDirectory points to domain directory,
860 // and AdditionalBooks field stores one entry that points to the open
861 // directory
862 auto dir = keylet::quality(keylet::book(book), uRate);
863 bool const bookExisted = static_cast<bool>(sb.peek(dir));
864
865 auto setBookDir = [&](SLE::ref sle,
866 std::optional<uint256> const& maybeDomain) {
867 sle->setFieldH160(sfTakerPaysCurrency, saTakerPays.issue().currency);
868 sle->setFieldH160(sfTakerPaysIssuer, saTakerPays.issue().account);
869 sle->setFieldH160(sfTakerGetsCurrency, saTakerGets.issue().currency);
870 sle->setFieldH160(sfTakerGetsIssuer, saTakerGets.issue().account);
871 sle->setFieldU64(sfExchangeRate, uRate);
872 if (maybeDomain)
873 sle->setFieldH256(sfDomainID, *maybeDomain);
874 };
875
876 auto const bookNode = sb.dirAppend(dir, offer_index, [&](SLE::ref sle) {
877 // sets domainID on book directory if it's a domain offer
878 setBookDir(sle, domainID);
879 });
880
881 if (!bookNode)
882 {
883 // LCOV_EXCL_START
884 JLOG(j_.debug()) << "final result: failed to add offer to book";
885 return {tecDIR_FULL, true};
886 // LCOV_EXCL_STOP
887 }
888
889 auto sleOffer = std::make_shared<SLE>(offer_index);
890 sleOffer->setAccountID(sfAccount, account_);
891 sleOffer->setFieldU32(sfSequence, offerSequence);
892 sleOffer->setFieldH256(sfBookDirectory, dir.key);
893 sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
894 sleOffer->setFieldAmount(sfTakerGets, saTakerGets);
895 sleOffer->setFieldU64(sfOwnerNode, *ownerNode);
896 sleOffer->setFieldU64(sfBookNode, *bookNode);
897 if (expiration)
898 sleOffer->setFieldU32(sfExpiration, *expiration);
899 if (bPassive)
900 sleOffer->setFlag(lsfPassive);
901 if (bSell)
902 sleOffer->setFlag(lsfSell);
903 if (domainID)
904 sleOffer->setFieldH256(sfDomainID, *domainID);
905
906 // if it's a hybrid offer, set hybrid flag, and create an open dir
907 if (bHybrid)
908 {
909 auto const res = applyHybrid(
910 sb, sleOffer, offer_index, saTakerPays, saTakerGets, setBookDir);
911 if (res != tesSUCCESS)
912 return {res, true}; // LCOV_EXCL_LINE
913 }
914
915 sb.insert(sleOffer);
916
917 if (!bookExisted)
919
920 JLOG(j_.debug()) << "final result: success";
921
922 return {tesSUCCESS, true};
923}
924
925TER
927{
928 // This is the ledger view that we work against. Transactions are applied
929 // as we go on processing transactions.
930 Sandbox sb(&ctx_.view());
931
932 // This is a ledger with just the fees paid and any unfunded or expired
933 // offers we encounter removed. It's used when handling Fill-or-Kill offers,
934 // if the order isn't going to be placed, to avoid wasting the work we did.
935 Sandbox sbCancel(&ctx_.view());
936
937 auto const result = applyGuts(sb, sbCancel);
938 if (result.second)
939 sb.apply(ctx_.rawView());
940 else
941 sbCancel.apply(ctx_.rawView());
942 return result.first;
943}
944
945} // 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:683
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:673
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:463
@ 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:290
@ tecNO_ISSUER
Definition TER.h:300
@ tecDIR_FULL
Definition TER.h:288
@ tecUNFUNDED_OFFER
Definition TER.h:285
@ tecFROZEN
Definition TER.h:304
@ tecKILLED
Definition TER.h:317
@ tecINTERNAL
Definition TER.h:311
@ tecNO_PERMISSION
Definition TER.h:306
@ tecNO_LINE
Definition TER.h:302
@ tecFAILED_PROCESSING
Definition TER.h:287
@ tecEXPIRED
Definition TER.h:315
@ tecNO_AUTH
Definition TER.h:301
@ tesSUCCESS
Definition TER.h:245
bool isTesSuccess(TER x) noexcept
Definition TER.h:678
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:685
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)