xrpld
Loading...
Searching...
No Matches
OfferCreate.cpp
1#include <xrpl/tx/transactors/dex/OfferCreate.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/base_uint.h>
5#include <xrpl/beast/utility/Zero.h>
6#include <xrpl/beast/utility/instrumentation.h>
7#include <xrpl/core/ServiceRegistry.h>
8#include <xrpl/ledger/ApplyView.h>
9#include <xrpl/ledger/PaymentSandbox.h>
10#include <xrpl/ledger/Sandbox.h>
11#include <xrpl/ledger/View.h>
12#include <xrpl/ledger/helpers/AccountRootHelpers.h>
13#include <xrpl/ledger/helpers/CredentialHelpers.h>
14#include <xrpl/ledger/helpers/DirectoryHelpers.h>
15#include <xrpl/ledger/helpers/MPTokenHelpers.h>
16#include <xrpl/ledger/helpers/OfferHelpers.h>
17#include <xrpl/ledger/helpers/PermissionedDEXHelpers.h>
18#include <xrpl/ledger/helpers/TokenHelpers.h>
19#include <xrpl/protocol/AccountID.h>
20#include <xrpl/protocol/Asset.h>
21#include <xrpl/protocol/Book.h>
22#include <xrpl/protocol/Feature.h>
23#include <xrpl/protocol/Indexes.h>
24#include <xrpl/protocol/Issue.h>
25#include <xrpl/protocol/Keylet.h>
26#include <xrpl/protocol/LedgerFormats.h>
27#include <xrpl/protocol/MPTIssue.h>
28#include <xrpl/protocol/Protocol.h>
29#include <xrpl/protocol/Quality.h>
30#include <xrpl/protocol/Rate.h>
31#include <xrpl/protocol/SField.h>
32#include <xrpl/protocol/STAmount.h>
33#include <xrpl/protocol/STArray.h>
34#include <xrpl/protocol/STLedgerEntry.h>
35#include <xrpl/protocol/STPathSet.h>
36#include <xrpl/protocol/STTx.h>
37#include <xrpl/protocol/SeqProxy.h>
38#include <xrpl/protocol/TER.h>
39#include <xrpl/protocol/TxFlags.h>
40#include <xrpl/protocol/UintTypes.h>
41#include <xrpl/protocol/XRPAmount.h>
42#include <xrpl/tx/Transactor.h>
43#include <xrpl/tx/applySteps.h>
44#include <xrpl/tx/paths/Flow.h>
45#include <xrpl/tx/paths/detail/Steps.h>
46
47#include <algorithm>
48#include <cstdint>
49#include <exception>
50#include <functional>
51#include <memory>
52#include <optional>
53#include <tuple>
54#include <utility>
55
56namespace xrpl {
59{
60 auto calculateMaxXRPSpend = [](STTx const& tx) -> XRPAmount {
61 auto const& amount{tx[sfTakerGets]};
62 return amount.native() ? amount.xrp() : beast::kZero;
63 };
64
65 return TxConsequences{ctx.tx, calculateMaxXRPSpend(ctx.tx)};
66}
67
68bool
70{
71 if (ctx.tx.isFieldPresent(sfDomainID) && !ctx.rules.enabled(featurePermissionedDEX))
72 return false;
73
74 return ctx.rules.enabled(featureMPTokensV2) ||
75 (!ctx.tx[sfTakerPays].holds<MPTIssue>() && !ctx.tx[sfTakerGets].holds<MPTIssue>());
76}
77
80{
81 // The tfOfferCreateMask is built assuming that PermissionedDEX is
82 // enabled
83 if (ctx.rules.enabled(featurePermissionedDEX))
84 return tfOfferCreateMask;
85 // If PermissionedDEX is not enabled, add tfHybrid to the mask,
86 // indicating it is not allowed.
87 return tfOfferCreateMask | tfHybrid;
88}
89
92{
93 auto& tx = ctx.tx;
94 auto& j = ctx.j;
95
96 if (tx.isFlag(tfHybrid) && !tx.isFieldPresent(sfDomainID))
97 return temINVALID_FLAG;
98
99 // A zero DomainID is invalid for a PermissionedDomain ledger entry because
100 // keylet::permissionedDomain(UInt256) uses the DomainID as the ledger key.
101 if (auto const domainID = tx[~sfDomainID];
102 ctx.rules.enabled(fixCleanup3_2_0) && domainID && *domainID == beast::kZero)
103 return temMALFORMED;
104
105 bool const bImmediateOrCancel(tx.isFlag(tfImmediateOrCancel));
106 bool const bFillOrKill(tx.isFlag(tfFillOrKill));
107
108 if (bImmediateOrCancel && bFillOrKill)
109 {
110 JLOG(j.debug()) << "Malformed transaction: both IoC and FoK set.";
111 return temINVALID_FLAG;
112 }
113
114 bool const bHaveExpiration(tx.isFieldPresent(sfExpiration));
115
116 if (bHaveExpiration && (tx.getFieldU32(sfExpiration) == 0))
117 {
118 JLOG(j.debug()) << "Malformed offer: bad expiration";
119 return temBAD_EXPIRATION;
120 }
121
122 if (auto const cancelSequence = tx[~sfOfferSequence]; cancelSequence && *cancelSequence == 0)
123 {
124 JLOG(j.debug()) << "Malformed offer: bad cancel sequence";
125 return temBAD_SEQUENCE;
126 }
127
128 STAmount const saTakerPays = tx[sfTakerPays];
129 STAmount const saTakerGets = tx[sfTakerGets];
130
131 if (!isLegalNet(saTakerPays) || !isLegalNet(saTakerGets))
132 return temBAD_AMOUNT;
133
134 if (saTakerPays.native() && saTakerGets.native())
135 {
136 JLOG(j.debug()) << "Malformed offer: redundant (XRP for XRP)";
137 return temBAD_OFFER;
138 }
139 if (saTakerPays <= beast::kZero || saTakerGets <= beast::kZero)
140 {
141 JLOG(j.debug()) << "Malformed offer: bad amount";
142 return temBAD_OFFER;
143 }
144
145 auto const& uPaysIssuerID = saTakerPays.getIssuer();
146 auto const& uPaysAsset = saTakerPays.asset();
147
148 auto const& uGetsIssuerID = saTakerGets.getIssuer();
149 auto const& uGetsAsset = saTakerGets.asset();
150
151 if (uPaysAsset == uGetsAsset)
152 {
153 JLOG(j.debug()) << "Malformed offer: redundant (IOU for IOU)";
154 return temREDUNDANT;
155 }
156 // We don't allow a non-native currency to use the currency code XRP.
157 if (badAsset() == uPaysAsset || badAsset() == uGetsAsset)
158 {
159 JLOG(j.debug()) << "Malformed offer: bad currency";
160 return temBAD_CURRENCY;
161 }
162
163 if (saTakerPays.native() != !uPaysIssuerID || saTakerGets.native() != !uGetsIssuerID)
164 {
165 JLOG(j.debug()) << "Malformed offer: bad issuer";
166 return temBAD_ISSUER;
167 }
168
169 return tesSUCCESS;
170}
171
172TER
174{
175 auto const id = ctx.tx[sfAccount];
176
177 auto saTakerPays = ctx.tx[sfTakerPays];
178 auto saTakerGets = ctx.tx[sfTakerGets];
179
180 auto const& uPaysAsset = saTakerPays.asset();
181
182 auto const cancelSequence = ctx.tx[~sfOfferSequence];
183
184 auto const sleCreator = ctx.view.read(keylet::account(id));
185 if (!sleCreator)
186 return terNO_ACCOUNT;
187
188 std::uint32_t const uAccountSequence = sleCreator->getFieldU32(sfSequence);
189
190 auto viewJ = ctx.registry.get().getJournal("View");
191
192 if (auto const ter = checkGlobalFrozen(ctx.view, saTakerPays.asset()); !isTesSuccess(ter))
193 {
194 JLOG(ctx.j.debug()) << "Offer involves frozen or locked asset";
195 return ter;
196 }
197 if (auto const ter = checkGlobalFrozen(ctx.view, saTakerGets.asset()); !isTesSuccess(ter))
198 {
199 JLOG(ctx.j.debug()) << "Offer involves frozen or locked asset";
200 return ter;
201 }
202
203 // Allow unfunded MPT for issuer (OutstandingAmount >= MaximumAmount)
204 if ((!saTakerGets.holds<MPTIssue>() || saTakerGets.getIssuer() != id) &&
206 ctx.view,
207 id,
208 saTakerGets,
211 viewJ) <= beast::kZero)
212 {
213 JLOG(ctx.j.debug()) << "delay: Offers must be at least partially funded.";
214 return tecUNFUNDED_OFFER;
215 }
216
217 // This can probably be simplified to make sure that you cancel sequences
218 // before the transaction sequence number.
219 if (cancelSequence && (uAccountSequence <= *cancelSequence))
220 {
221 JLOG(ctx.j.debug()) << "uAccountSequenceNext=" << uAccountSequence
222 << " uOfferSequence=" << *cancelSequence;
223 return temBAD_SEQUENCE;
224 }
225
226 if (hasExpired(ctx.view, ctx.tx[~sfExpiration]))
227 {
228 // Note that this will get checked again in applyGuts, but it saves
229 // us a call to checkAcceptAsset and possible false negative.
230 return tecEXPIRED;
231 }
232
233 // Make sure that we are authorized to hold what the taker will pay us.
234 if (!saTakerPays.native())
235 {
236 auto result = checkAcceptAsset(ctx.view, ctx.flags, id, ctx.j, uPaysAsset);
237 if (!isTesSuccess(result))
238 return result;
239 }
240
241 // if domain is specified, make sure that domain exists and the offer create
242 // is part of the domain
243 if (ctx.tx.isFieldPresent(sfDomainID))
244 {
245 if (ctx.view.rules().enabled(fixCleanup3_4_0))
246 {
247 auto const domainID = ctx.tx[sfDomainID];
248 auto const sleDomain = ctx.view.read(keylet::permissionedDomain(domainID));
249 if (!sleDomain)
250 return tecNO_PERMISSION;
251
252 // Domain owner is always considered in the domain, no credential check
253 // needed. For all other accounts, use validDomain which detects expired
254 // credentials. Suppress tecEXPIRED here so doApply can run and delete
255 // the expired credential SLEs from the ledger.
256 if (sleDomain->getAccountID(sfOwner) != id)
257 {
258 // validDomain returns tecNO_AUTH when no matching credential is
259 // found. Map it to tecNO_PERMISSION to preserve existing behavior.
260 if (auto const err = credentials::validDomain(ctx.view, domainID, id);
261 !isTesSuccess(err) && err != tecEXPIRED)
262 return tecNO_PERMISSION;
263 }
264 }
265 else
266 {
267 if (!permissioned_dex::accountInDomain(ctx.view, id, ctx.tx[sfDomainID]))
268 return tecNO_PERMISSION;
269 }
270 }
271
272 if (auto const ter = canTrade(ctx.view, saTakerPays.asset()); !isTesSuccess(ter))
273 return ter;
274 if (auto const ter = canTrade(ctx.view, saTakerGets.asset()); !isTesSuccess(ter))
275 return ter;
276
277 return tesSUCCESS;
278}
279
280TER
282 ReadView const& view,
283 ApplyFlags const flags,
284 AccountID const id,
285 beast::Journal const j,
286 Asset const& asset)
287{
288 // Only valid for custom currencies
289 XRPL_ASSERT(!isXRP(asset), "xrpl::OfferCreate::checkAcceptAsset : input is not XRP");
290
291 auto const issuerAccount = view.read(keylet::account(asset.getIssuer()));
292
293 if (!issuerAccount)
294 {
295 JLOG(j.debug()) << "delay: can't receive IOUs from non-existent issuer: "
296 << to_string(asset.getIssuer());
297
298 return ((flags & TapRetry) != 0u) ? TER{terNO_ACCOUNT} : TER{tecNO_ISSUER};
299 }
300
301 // An account cannot create a trustline to itself, so no line can exist
302 // to be frozen. Additionally, an issuer can always accept its own
303 // issuance.
304 if (asset.getIssuer() == id)
305 return tesSUCCESS;
306
307 return asset.visit(
308 [&](Issue const& issue) -> TER {
309 auto const& issuer = issue.getIssuer();
310 auto const trustLine = view.read(keylet::trustLine(id, issuer, issue.currency));
311
312 // Check if the issuer has lsfDisallowIncomingTrustline set.
313 // If so, the account must already have a trustline to receive tokens.
314 if (view.rules().enabled(fixCleanup3_4_0) &&
315 issuerAccount->isFlag(lsfDisallowIncomingTrustline))
316 {
317 if (!trustLine)
318 {
319 JLOG(j.debug()) << "delay: can't receive IOUs from issuer with "
320 "DisallowIncomingTrustline set";
321 return ((flags & TapRetry) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE};
322 }
323 }
324
325 if (issuerAccount->isFlag(lsfRequireAuth))
326 {
327 if (!trustLine)
328 {
329 return ((flags & TapRetry) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE};
330 }
331
332 // Entries have a canonical representation, determined by a
333 // lexicographical "greater than" comparison employing
334 // strict weak ordering. Determine which entry we need to
335 // access.
336 bool const canonicalGt(id > issuer);
337
338 bool const isAuthorized(trustLine->isFlag(canonicalGt ? lsfLowAuth : lsfHighAuth));
339
340 if (!isAuthorized)
341 {
342 JLOG(j.debug()) << "delay: can't receive IOUs from "
343 "issuer without auth.";
344
345 return ((flags & TapRetry) != 0u) ? TER{terNO_AUTH} : TER{tecNO_AUTH};
346 }
347 }
348
349 if (!trustLine)
350 {
351 return tesSUCCESS;
352 }
353
354 // There's no difference which side enacted deep freeze, accepting
355 // tokens shouldn't be possible.
356 bool const deepFrozen =
357 ((*trustLine)[sfFlags] & (lsfLowDeepFreeze | lsfHighDeepFreeze)) != 0u;
358
359 if (deepFrozen)
360 {
361 return tecFROZEN;
362 }
363
364 return tesSUCCESS;
365 },
366 [&](MPTIssue const& issue) -> TER {
367 // WeakAuth - don't check if MPToken exists since it's created
368 // if needed.
369 if (auto const ter = requireAuth(view, issue, id, AuthType::WeakAuth);
370 !isTesSuccess(ter))
371 {
372 return ter;
373 }
374
375 return checkFrozen(view, id, issue);
376 });
377}
378
381 PaymentSandbox& psb,
382 PaymentSandbox& psbCancel,
383 Amounts const& takerAmount,
384 std::optional<UInt256> const& domainID)
385{
386 try
387 {
388 // If the taker is unfunded before we begin crossing there's nothing
389 // to do - just return an error.
390 //
391 // We check this in preclaim, but when selling XRP charged fees can
392 // cause a user's available balance to go to 0 (by causing it to dip
393 // below the reserve) so we check this case again.
394 STAmount const inStartBalance = accountFunds(
395 psb,
397 takerAmount.in,
400 j_);
401 // Allow unfunded MPT issuer
402 auto const disallowUnfunded =
403 !inStartBalance.holds<MPTIssue>() || inStartBalance.getIssuer() != accountID_;
404 if (disallowUnfunded && inStartBalance <= beast::kZero)
405 {
406 // The account balance can't cover even part of the offer.
407 JLOG(j_.debug()) << "Not crossing: taker is unfunded.";
408 return {tecUNFUNDED_OFFER, takerAmount};
409 }
410
411 // If the gateway has a transfer rate, accommodate that. The
412 // gateway takes its cut without any special consent from the
413 // offer taker. Set sendMax to allow for the gateway's cut.
414 Rate gatewayXferRate{QUALITY_ONE};
415 STAmount sendMax = takerAmount.in;
416 if (!sendMax.native() && (accountID_ != sendMax.getIssuer()))
417 {
418 gatewayXferRate = transferRate(psb, sendMax);
419 if (gatewayXferRate.value != QUALITY_ONE)
420 {
421 sendMax =
422 multiplyRound(takerAmount.in, gatewayXferRate, takerAmount.in.asset(), true);
423 }
424 }
425
426 // Payment flow code compares quality after the transfer rate is
427 // included. Since transfer rate is incorporated compute threshold.
428 Quality threshold{takerAmount.out, sendMax};
429
430 // If we're creating a passive offer adjust the threshold so we only
431 // cross offers that have a better quality than this one.
432 if (ctx_.tx.isFlag(tfPassive))
433 ++threshold;
434
435 // Don't send more than our balance.
436 if (sendMax > inStartBalance)
437 sendMax = inStartBalance;
438
439 // Always invoke flow() with the default path. However if neither
440 // of the takerAmount currencies are XRP then we cross through an
441 // additional path with XRP as the intermediate between two books.
442 // This second path we have to build ourselves.
443 STPathSet paths;
444 if (!takerAmount.in.native() && !takerAmount.out.native())
445 {
446 STPath path;
447 path.emplaceBack(std::nullopt, xrpCurrency(), std::nullopt);
448 paths.emplaceBack(std::move(path));
449 }
450 // Special handling for the tfSell flag.
451 STAmount deliver = takerAmount.out;
452 auto const& deliverAsset = deliver.asset();
453 OfferCrossing offerCrossing = OfferCrossing::Yes;
454 if (ctx_.tx.isFlag(tfSell))
455 {
456 offerCrossing = OfferCrossing::Sell;
457 // We are selling, so we will accept *more* than the offer
458 // specified. Since we don't know how much they might offer,
459 // we allow delivery of the largest possible amount.
460 deliver.asset().visit(
461 [&](Issue const& issue) {
462 if (issue.native())
463 {
465 }
466 // We can't use the maximum possible currency here because
467 // there might be a gateway transfer rate to account for.
468 // Since the transfer rate cannot exceed 200%, we use 1/2
469 // maxValue for our limit.
470 else
471 {
472 deliver =
474 }
475 },
476 [&](MPTIssue const&) { deliver = STAmount{deliverAsset, kMaxMpTokenAmount / 2}; });
477 }
478
479 // Call the payment engine's flow() to do the actual work.
480 auto const result = flow(
481 psb,
482 deliver,
485 paths,
486 true, // default path
487 !ctx_.tx.isFlag(tfFillOrKill), // partial payment
488 true, // owner pays transfer fee
489 offerCrossing,
490 threshold,
491 sendMax,
492 domainID,
493 j_);
494
495 // If stale offers were found remove them.
496 for (auto const& toRemove : result.removableOffers)
497 {
498 if (auto otr = psb.peek(keylet::offer(toRemove)))
499 offerDelete(psb, otr, j_);
500 if (auto otr = psbCancel.peek(keylet::offer(toRemove)))
501 offerDelete(psbCancel, otr, j_);
502 }
503
504 // Determine the size of the final offer after crossing.
505 auto afterCross = takerAmount; // If !tesSUCCESS offer unchanged
506 if (isTesSuccess(result.result()))
507 {
508 STAmount const takerInBalance = accountFunds(
509 psb,
511 takerAmount.in,
514 j_);
515
516 if (disallowUnfunded && takerInBalance <= beast::kZero)
517 {
518 // If offer crossing exhausted the account's funds don't
519 // create the offer.
520 afterCross.in.clear();
521 afterCross.out.clear();
522 }
523 else
524 {
525 STAmount const rate{Quality{takerAmount.out, takerAmount.in}.rate()};
526
527 if (ctx_.tx.isFlag(tfSell))
528 {
529 // If selling then scale the new out amount based on how
530 // much we sold during crossing. This preserves the offer
531 // Quality,
532
533 // Reduce the offer that is placed by the crossed amount.
534 // Note that we must ignore the portion of the
535 // actualAmountIn that may have been consumed by a
536 // gateway's transfer rate.
537 STAmount nonGatewayAmountIn = result.actualAmountIn;
538 if (gatewayXferRate.value != QUALITY_ONE)
539 {
540 nonGatewayAmountIn = divideRound(
541 result.actualAmountIn, gatewayXferRate, takerAmount.in.asset(), true);
542 }
543
544 afterCross.in -= nonGatewayAmountIn;
545
546 // It's possible that the divRound will cause our subtract
547 // to go slightly negative. So limit afterCross.in to beast::kZero.
548 if (afterCross.in < beast::kZero)
549 {
550 // We should verify that the difference *is* small, but
551 // what is a good threshold to check?
552 afterCross.in.clear();
553 }
554
555 afterCross.out =
556 divRoundStrict(afterCross.in, rate, takerAmount.out.asset(), false);
557 }
558 else
559 {
560 // If not selling, we scale the input based on the
561 // remaining output. This too preserves the offer
562 // Quality.
563 afterCross.out -= result.actualAmountOut;
564 XRPL_ASSERT(
565 afterCross.out >= beast::kZero,
566 "xrpl::OfferCreate::flowCross : minimum offer");
567 if (afterCross.out < beast::kZero)
568 afterCross.out.clear();
569 afterCross.in = mulRound(afterCross.out, rate, takerAmount.in.asset(), true);
570 }
571 }
572 }
573
574 // Return how much of the offer is left.
575 return {tesSUCCESS, afterCross};
576 }
577 catch (std::exception const& e)
578 {
579 JLOG(j_.error()) << "Exception during offer crossing: " << e.what();
580 }
581 return {tecINTERNAL, takerAmount};
582}
583
586{
587 std::string txt = amount.getText();
588 txt += "/";
589 amount.asset().visit(
590 [&](Issue const& issue) { txt += to_string(issue.currency); },
591 [&](MPTIssue const& issue) { txt += to_string(issue); });
592 return txt;
593}
594
595TER
597 Sandbox& sb,
598 STLedgerEntry::pointer sleOffer,
599 Keylet const& offerKey,
600 STAmount const& saTakerPays,
601 STAmount const& saTakerGets,
602 std::uint64_t openRate,
603 std::function<void(SLE::Ref, std::optional<UInt256>)> const& setDir)
604{
605 if (!sleOffer->isFieldPresent(sfDomainID))
606 return tecINTERNAL; // LCOV_EXCL_LINE
607
608 // set hybrid flag
609 sleOffer->setFlag(lsfHybrid);
610
611 // if offer is hybrid, need to also place into open offer dir
612 Book const book{saTakerPays.asset(), saTakerGets.asset(), std::nullopt};
613
614 auto dir = keylet::quality(keylet::book(book), openRate);
615 bool const bookExists = sb.exists(dir);
616
617 auto const bookNode = sb.dirAppend(dir, offerKey, [&](SLE::Ref sle) {
618 // don't set domainID on the directory object since this directory is
619 // for open book
620 setDir(sle, std::nullopt);
621 });
622
623 if (!bookNode)
624 {
625 JLOG(j_.debug()) << "final result: failed to add hybrid offer to open book";
626 return tecDIR_FULL; // LCOV_EXCL_LINE
627 }
628
629 STArray bookArr(sfAdditionalBooks, 1);
630 auto bookInfo = STObject::makeInnerObject(sfBook);
631 bookInfo.setFieldH256(sfBookDirectory, dir.key);
632 bookInfo.setFieldU64(sfBookNode, *bookNode);
633 bookArr.pushBack(std::move(bookInfo));
634
635 if (!bookExists)
636 ctx_.addOrderBook(book);
637
638 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
639 return tesSUCCESS;
640}
641
644{
645 using beast::kZero;
646
647 bool const bPassive(ctx_.tx.isFlag(tfPassive));
648 bool const bImmediateOrCancel(ctx_.tx.isFlag(tfImmediateOrCancel));
649 bool const bFillOrKill(ctx_.tx.isFlag(tfFillOrKill));
650 bool const bSell(ctx_.tx.isFlag(tfSell));
651 bool const bHybrid(ctx_.tx.isFlag(tfHybrid));
652
653 auto saTakerPays = ctx_.tx[sfTakerPays];
654 auto saTakerGets = ctx_.tx[sfTakerGets];
655 auto const domainID = ctx_.tx[~sfDomainID];
656
657 auto const cancelSequence = ctx_.tx[~sfOfferSequence];
658
659 // Note that we use the value from the sequence or ticket as the
660 // offer sequence. For more explanation see comments in SeqProxy.h.
661 auto const offerSequence = ctx_.tx.getSeqProxy();
662
663 // This is the original rate of the offer, and is the rate at which
664 // it will be placed, even if crossing offers change the amounts that
665 // end up on the books.
666 auto uRate = getRate(saTakerGets, saTakerPays);
667
668 auto viewJ = ctx_.registry.get().getJournal("View");
669
670 TER result = tesSUCCESS;
671
672 // Process a cancellation request that's passed along with an offer.
673 if (cancelSequence)
674 {
675 auto const seqProxy = SeqProxy::rawSequence(*cancelSequence);
676 auto const sleCancel = sb.peek(keylet::offer(accountID_, seqProxy));
677
678 // It's not an error to not find the offer to cancel: it might have
679 // been consumed or removed. If it is found, however, it's an error
680 // to fail to delete it.
681 if (sleCancel)
682 {
683 JLOG(j_.debug()) << "Create cancels order " << *cancelSequence;
684 result = offerDelete(sb, sleCancel, viewJ);
685 }
686 }
687
688 auto const expiration = ctx_.tx[~sfExpiration];
689
690 if (hasExpired(sb, expiration))
691 {
692 // If the offer has expired, the transaction has successfully
693 // done nothing, so short circuit from here.
694 return {tecEXPIRED, true};
695 }
696
697 bool crossed = false;
698 bool const mptV2 = ctx_.view().rules().enabled(featureMPTokensV2);
699
700 if (isTesSuccess(result))
701 {
702 // If a tick size applies, round the offer to the tick size
703 auto const& uPaysIssuerID = saTakerPays.getIssuer();
704 auto const& uGetsIssuerID = saTakerGets.getIssuer();
705
707 // Not XRP or MPT
708 if (!saTakerPays.integral())
709 {
710 auto const sle = sb.read(keylet::account(uPaysIssuerID));
711 if (sle && sle->isFieldPresent(sfTickSize))
712 uTickSize = std::min(uTickSize, (*sle)[sfTickSize]);
713 }
714 // Not XRP or MPT
715 if (!saTakerGets.integral())
716 {
717 auto const sle = sb.read(keylet::account(uGetsIssuerID));
718 if (sle && sle->isFieldPresent(sfTickSize))
719 uTickSize = std::min(uTickSize, (*sle)[sfTickSize]);
720 }
721 // Quality's ctor is the same getRate() call that produced uRate, and
722 // round() maps zero to zero, so an unrepresentable quality would make
723 // divide() below throw (tefEXCEPTION). Skip the rounding instead: the
724 // offer still crosses, and any residual is stopped before placement.
725 bool const unrepresentableRate = mptV2 && uRate == 0;
726 if (uTickSize < Quality::kMaxTickSize && !unrepresentableRate)
727 {
728 auto const rate = Quality{saTakerGets, saTakerPays}.round(uTickSize).rate();
729
730 // We round the side that's not exact,
731 // just as if the offer happened to execute
732 // at a slightly better (for the placer) rate
733 if (bSell)
734 {
735 // this is a sell, round taker pays
736 if (!saTakerPays.holds<MPTIssue>())
737 saTakerPays = multiply(saTakerGets, rate, saTakerPays.asset());
738 }
739 else if (!saTakerGets.holds<MPTIssue>())
740 {
741 // this is a buy, round taker gets
742 saTakerGets = divide(saTakerPays, rate, saTakerGets.asset());
743 }
744 if (!saTakerGets || !saTakerPays)
745 {
746 JLOG(j_.debug()) << "Offer rounded to zero";
747 return {result, true};
748 }
749
750 uRate = getRate(saTakerGets, saTakerPays);
751 }
752
753 // We reverse pays and gets because during crossing we are taking.
754 Amounts const takerAmount(saTakerGets, saTakerPays);
755
756 JLOG(j_.debug()) << "Attempting cross: " << to_string(takerAmount.in.asset()) << " -> "
757 << to_string(takerAmount.out.asset());
758
759 if (auto stream = j_.trace())
760 {
761 stream << " mode: " << (bPassive ? "passive " : "") << (bSell ? "sell" : "buy");
762 stream << " in: " << formatAmount(takerAmount.in);
763 stream << " out: " << formatAmount(takerAmount.out);
764 }
765
766 // The amount of the offer that is unfilled after crossing has been
767 // performed. It may be equal to the original amount (didn't cross),
768 // empty (fully crossed), or something in-between.
769 Amounts placeOffer;
770 PaymentSandbox psbFlow{&sb};
771 PaymentSandbox psbCancelFlow{&sbCancel};
772
773 std::tie(result, placeOffer) = flowCross(psbFlow, psbCancelFlow, takerAmount, domainID);
774 psbFlow.apply(sb);
775 psbCancelFlow.apply(sbCancel);
776
777 // We expect the implementation of cross to succeed
778 // or give a tec.
779 XRPL_ASSERT(
780 isTesSuccess(result) || isTecClaim(result),
781 "xrpl::OfferCreate::applyGuts : result is tesSUCCESS or "
782 "tecCLAIM");
783
784 if (auto stream = j_.trace())
785 {
786 stream << "Cross result: " << transToken(result);
787 stream << " in: " << formatAmount(placeOffer.in);
788 stream << " out: " << formatAmount(placeOffer.out);
789 }
790
791 if (result == tecFAILED_PROCESSING && sb.open())
792 result = telFAILED_PROCESSING;
793
794 if (!isTesSuccess(result))
795 {
796 JLOG(j_.debug()) << "final result: " << transToken(result);
797 return {result, true};
798 }
799
800 XRPL_ASSERT(
801 saTakerGets.asset() == placeOffer.in.asset(),
802 "xrpl::OfferCreate::applyGuts : taker gets issue match");
803 XRPL_ASSERT(
804 saTakerPays.asset() == placeOffer.out.asset(),
805 "xrpl::OfferCreate::applyGuts : taker pays issue match");
806
807 if (takerAmount != placeOffer)
808 crossed = true;
809
810 // The offer that we need to place after offer crossing should
811 // never be negative. If it is, something went very very wrong.
812 if (placeOffer.in < kZero || placeOffer.out < kZero)
813 {
814 JLOG(j_.fatal()) << "Cross left offer negative!"
815 << " in: " << formatAmount(placeOffer.in)
816 << " out: " << formatAmount(placeOffer.out);
817 return {tefINTERNAL, true};
818 }
819
820 if (placeOffer.in == kZero || placeOffer.out == kZero)
821 {
822 JLOG(j_.debug()) << "Offer fully crossed!";
823 return {result, true};
824 }
825
826 // We now need to adjust the offer to reflect the amount left after
827 // crossing. We reverse in and out here, since during crossing we
828 // were the taker.
829 saTakerPays = placeOffer.out;
830 saTakerGets = placeOffer.in;
831 }
832
833 XRPL_ASSERT(
834 saTakerPays > beast::kZero && saTakerGets > beast::kZero,
835 "xrpl::OfferCreate::applyGuts : taker pays and gets positive");
836
837 if (!isTesSuccess(result))
838 {
839 JLOG(j_.debug()) << "final result: " << transToken(result);
840 return {result, true};
841 }
842
843 if (auto stream = j_.trace())
844 {
845 stream << "Place" << (crossed ? " remaining " : " ") << "offer:";
846 stream << " Pays: " << saTakerPays.getFullText();
847 stream << " Gets: " << saTakerGets.getFullText();
848 }
849
850 // For 'fill or kill' offers, failure to fully cross means that the
851 // entire operation should be aborted, with only fees paid.
852 if (bFillOrKill)
853 {
854 JLOG(j_.trace()) << "Fill or Kill: offer killed";
855 return {tecKILLED, false};
856 }
857
858 // For 'immediate or cancel' offers, the amount remaining doesn't get
859 // placed - it gets canceled and the operation succeeds.
860 if (bImmediateOrCancel)
861 {
862 JLOG(j_.trace()) << "Immediate or cancel: offer canceled";
863 if (!crossed)
864 {
865 // Any ImmediateOrCancel offer that transfers absolutely no funds
866 // returns tecKILLED rather than tesSUCCESS. Motivation for the
867 // change is here: https://github.com/XRPLF/rippled/issues/4115
868 return {tecKILLED, false};
869 }
870 return {tesSUCCESS, true};
871 }
872
873 // The remainder rests at uRate, the original pre-crossing rate. A zero
874 // rate (quality not representable) puts it in the directory whose index
875 // equals getBookBase(book), and BookTip scans keys strictly greater, so it
876 // could never be crossed while holding the owner's reserve. Don't place
877 // it; anything that crossed is kept, and a fully crossed offer has already
878 // returned above. Gated to preserve pre-amendment behavior.
879 if (mptV2 && uRate == 0)
880 {
881 JLOG(j_.debug()) << "Unrepresentable quality: remainder not placed";
882 if (!crossed)
883 return {tecKILLED, false};
884 return {tesSUCCESS, true};
885 }
886
887 auto const sleCreator = sb.peek(keylet::account(accountID_));
888 if (!sleCreator)
889 return {tefINTERNAL, false};
890
891 {
892 XRPAmount const reserve = accountReserve(sb, sleCreator, viewJ, {.ownerCountDelta = 1});
893 if (preFeeBalance_ < reserve)
894 {
895 // If we are here, the signing account had an insufficient reserve
896 // *prior* to our processing. If something actually crossed, then
897 // we allow this; otherwise, we just claim a fee.
898 if (!crossed)
899 result = tecINSUF_RESERVE_OFFER;
900
901 if (!isTesSuccess(result))
902 {
903 JLOG(j_.debug()) << "final result: " << transToken(result);
904 }
905
906 return {result, true};
907 }
908 }
909
910 // We need to place the remainder of the offer into its order book.
911 auto const offerIndex = keylet::offer(accountID_, offerSequence);
912
913 // Add offer to owner's directory.
914 auto const ownerNode =
916
917 if (!ownerNode)
918 {
919 // LCOV_EXCL_START
920 JLOG(j_.debug()) << "final result: failed to add offer to owner's directory";
921 return {tecDIR_FULL, true};
922 // LCOV_EXCL_STOP
923 }
924
925 // Update owner count.
926 increaseOwnerCount(sb, sleCreator, {}, 1, viewJ);
927
928 JLOG(j_.trace()) << "adding to book: " << to_string(saTakerPays.asset()) << " : "
929 << to_string(saTakerGets.asset())
930 << (domainID ? (" : " + to_string(*domainID)) : "");
931
932 Book const book{saTakerPays.asset(), saTakerGets.asset(), domainID};
933
934 // Add offer to order book, using the original rate
935 // before any crossing occurred.
936 //
937 // Regular offer - BookDirectory points to open directory
938 //
939 // Domain offer (w/o hybrid) - BookDirectory points to domain
940 // directory
941 //
942 // Hybrid domain offer - BookDirectory points to domain directory,
943 // and AdditionalBooks field stores one entry that points to the open
944 // directory
945 auto dir = keylet::quality(keylet::book(book), uRate);
946 bool const bookExisted = static_cast<bool>(sb.peek(dir));
947
948 auto setBookDir = [&](SLE::Ref sle, std::optional<UInt256> const& maybeDomain) {
949 saTakerPays.asset().visit(
950 [&](Issue const& issue) {
951 sle->setFieldH160(sfTakerPaysCurrency, issue.currency);
952 sle->setFieldH160(sfTakerPaysIssuer, issue.account);
953 },
954 [&](MPTIssue const& issue) { sle->setFieldH192(sfTakerPaysMPT, issue.getMptID()); });
955 saTakerGets.asset().visit(
956 [&](Issue const& issue) {
957 sle->setFieldH160(sfTakerGetsCurrency, issue.currency);
958 sle->setFieldH160(sfTakerGetsIssuer, issue.account);
959 },
960 [&](MPTIssue const& issue) { sle->setFieldH192(sfTakerGetsMPT, issue.getMptID()); });
961 sle->setFieldU64(sfExchangeRate, uRate);
962 if (maybeDomain)
963 sle->setFieldH256(sfDomainID, *maybeDomain);
964 };
965
966 auto const bookNode = sb.dirAppend(dir, offerIndex, [&](SLE::Ref sle) {
967 // sets domainID on book directory if it's a domain offer
968 setBookDir(sle, domainID);
969 });
970
971 if (!bookNode)
972 {
973 // LCOV_EXCL_START
974 JLOG(j_.debug()) << "final result: failed to add offer to book";
975 return {tecDIR_FULL, true};
976 // LCOV_EXCL_STOP
977 }
978
979 auto sleOffer = std::make_shared<SLE>(offerIndex);
980 sleOffer->setAccountID(sfAccount, accountID_);
981 sleOffer->setFieldU32(sfSequence, offerSequence.value());
982 sleOffer->setFieldH256(sfBookDirectory, dir.key);
983 sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
984 sleOffer->setFieldAmount(sfTakerGets, saTakerGets);
985 sleOffer->setFieldU64(sfOwnerNode, *ownerNode);
986 sleOffer->setFieldU64(sfBookNode, *bookNode);
987 if (expiration)
988 sleOffer->setFieldU32(sfExpiration, *expiration);
989 if (bPassive)
990 sleOffer->setFlag(lsfPassive);
991 if (bSell)
992 sleOffer->setFlag(lsfSell);
993 if (domainID)
994 sleOffer->setFieldH256(sfDomainID, *domainID);
995
996 // if it's a hybrid offer, set hybrid flag, and create an open dir
997 if (bHybrid)
998 {
999 // Pre-fixCleanup3_2_0: the open-book directory quality was computed
1000 // from post-crossing amounts, which may differ from the original rate
1001 // due to rounding in rate preservation. Post-fixCleanup3_2_0: use the
1002 // original placement rate so the open-book directory quality matches
1003 // the domain-book directory.
1004 auto const openRate = ctx_.view().rules().enabled(fixCleanup3_2_0)
1005 ? uRate
1006 : getRate(saTakerGets, saTakerPays);
1007 auto const res =
1008 applyHybrid(sb, sleOffer, offerIndex, saTakerPays, saTakerGets, openRate, setBookDir);
1009 if (!isTesSuccess(res))
1010 return {res, true}; // LCOV_EXCL_LINE
1011 }
1012
1013 sb.insert(sleOffer);
1014
1015 if (!bookExisted)
1016 ctx_.addOrderBook(book);
1017
1018 JLOG(j_.debug()) << "final result: success";
1019
1020 return {tesSUCCESS, true};
1021}
1022
1023TER
1025{
1026 // If a DomainID is present, verify the account is still in the domain and
1027 // delete any expired credential SLEs. This must happen before the Sandboxes
1028 // are created: if we return a tec error, the engine applies sbCancel (not
1029 // sb) to rawView, so deletions made inside sb would be lost. Deletions made
1030 // directly to ctx_.view() here are preserved regardless of which branch
1031 // applyGuts takes.
1032 if (ctx_.tx.isFieldPresent(sfDomainID) && ctx_.view().rules().enabled(fixCleanup3_4_0))
1033 {
1034 auto const domainID = ctx_.tx[sfDomainID];
1035 auto const sleDomain = ctx_.view().read(keylet::permissionedDomain(domainID));
1036 if (!sleDomain)
1037 return tecINTERNAL; // LCOV_EXCL_LINE
1038
1039 if (sleDomain->getAccountID(sfOwner) != accountID_)
1040 {
1041 if (auto const err = verifyValidDomain(ctx_.view(), accountID_, domainID, j_);
1042 !isTesSuccess(err))
1043 return err;
1044 }
1045 }
1046
1047 // This is the ledger view that we work against. Transactions are applied
1048 // as we go on processing transactions.
1049 Sandbox sb(&ctx_.view());
1050
1051 // This is a ledger with just the fees paid and any unfunded or expired
1052 // offers we encounter removed. It's used when handling Fill-or-Kill offers,
1053 // if the order isn't going to be placed, to avoid wasting the work we did.
1054 Sandbox sbCancel(&ctx_.view());
1055
1056 auto const result = applyGuts(sb, sbCancel);
1057 if (result.second)
1058 {
1059 sb.apply(ctx_.rawView());
1060 }
1061 else
1062 {
1063 sbCancel.apply(ctx_.rawView());
1064 }
1065 return result.first;
1066}
1067
1068void
1070{
1071 // No transaction-specific invariants yet (future work).
1072}
1073
1074bool
1076{
1077 // No transaction-specific invariants yet (future work).
1078 return true;
1079}
1080
1081} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
Stream debug() const
Definition Journal.h:344
std::optional< std::uint64_t > dirInsert(Keylet const &directory, UInt256 const &key, std::function< void(SLE::Ref)> const &describe)
Insert an entry to a directory.
Definition ApplyView.h:373
std::optional< std::uint64_t > dirAppend(Keylet const &directory, Keylet const &key, std::function< void(SLE::Ref)> const &describe)
Append an entry to a directory.
Definition ApplyView.h:333
constexpr auto visit(Visitors &&... visitors) const -> decltype(auto)
Definition Asset.h:117
AccountID const & getIssuer() const
Definition Asset.cpp:21
Specifies an order book.
Definition Book.h:28
A currency issued by an account.
Definition Issue.h:18
Currency currency
Definition Issue.h:20
AccountID account
Definition Issue.h:21
bool native() const
Definition Issue.cpp:54
AccountID const & getIssuer() const
Definition Issue.h:30
AccountID const & getIssuer() const
Definition MPTIssue.cpp:29
static TER preclaim(PreclaimContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
static TER checkAcceptAsset(ReadView const &view, ApplyFlags const flags, AccountID const id, beast::Journal const j, Asset const &asset)
bool finalizeInvariants(STTx const &tx, TER result, XRPAmount fee, ReadView const &view, beast::Journal const &j) override
Check transaction-specific post-conditions after all entries have been visited.
TER doApply() override
Precondition: fee collection is likely.
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
static bool checkExtraFeatures(PreflightContext const &ctx)
void visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override
Inspect a single ledger entry modified by this transaction.
static NotTEC preflight(PreflightContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
std::pair< TER, bool > applyGuts(Sandbox &view, Sandbox &viewCancel)
std::pair< TER, Amounts > flowCross(PaymentSandbox &psb, PaymentSandbox &psbCancel, Amounts const &takerAmount, std::optional< UInt256 > const &domainID)
static std::string formatAmount(STAmount const &amount)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
TER applyHybrid(Sandbox &sb, STLedgerEntry::pointer sleOffer, Keylet const &offerIndex, STAmount const &saTakerPays, STAmount const &saTakerGets, std::uint64_t openRate, std::function< void(SLE::Ref, std::optional< UInt256 >)> const &setDir)
A wrapper which makes credits unavailable to balances.
void apply(RawView &to)
Apply changes to base view.
Represents the logical ratio of output currency to input currency.
Definition Quality.h:90
static int const kMaxTickSize
Definition Quality.h:97
STAmount rate() const
Returns the quality as STAmount.
Definition Quality.h:162
Quality round(int tickSize) const
Returns the quality rounded up to the specified number of decimal digits.
Definition Quality.cpp:134
A view into a ledger.
Definition ReadView.h:41
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
bool enabled(UInt256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:182
constexpr bool holds() const noexcept
Definition STAmount.h:478
std::string getFullText() const override
Definition STAmount.cpp:637
std::string getText() const override
Definition STAmount.cpp:647
bool integral() const noexcept
Definition STAmount.h:465
bool native() const noexcept
Definition STAmount.h:471
Asset const & asset() const
Definition STAmount.h:496
AccountID const & getIssuer() const
Definition STAmount.h:516
static constexpr std::uint64_t kMaxValue
Definition STAmount.h:67
static constexpr std::uint64_t kMaxNative
Definition STAmount.h:69
static constexpr int kMaxOffset
Definition STAmount.h:62
void pushBack(STObject const &object)
Definition STArray.h:212
std::shared_ptr< STLedgerEntry > pointer
std::shared_ptr< STLedgerEntry const > const & ConstRef
std::shared_ptr< STLedgerEntry > const & Ref
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:464
static STObject makeInnerObject(SField const &name)
Definition STObject.cpp:79
bool emplaceBack(Args &&... args)
emplaceBack adds a path to the set.
Definition STPathSet.h:676
Discardable, editable view to a ledger.
Definition Sandbox.h:18
void apply(RawView &to)
Definition Sandbox.h:38
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
Definition SeqProxy.h:62
beast::Journal const j_
Definition Transactor.h:164
ApplyView & view()
Definition Transactor.h:184
AccountID const accountID_
Definition Transactor.h:166
XRPAmount preFeeBalance_
Definition Transactor.h:167
ApplyContext & ctx_
Definition Transactor.h:162
Class describing the consequences to the account of applying a transaction if the transaction consume...
Definition applySteps.h:53
void insert(SLE::Ref sle) override
Insert a new state SLE.
SLE::pointer peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
bool open() const override
Returns true if this reflects an open ledger.
SLE::const_pointer read(Keylet const &k) const override
Return the state item associated with a key.
bool exists(Keylet const &k) const override
Determine if a state item exists.
T make_shared(T... args)
T min(T... args)
constexpr Zero kZero
Definition Zero.h:30
TER validDomain(ReadView const &view, UInt256 domainID, AccountID const &subject)
Keylet quality(Keylet const &k, std::uint64_t const q) noexcept
The initial directory page for a specific quality.
Definition Indexes.cpp:304
Keylet offer(AccountID const &id, SeqProxy const &seq) noexcept
An offer from an account.
Definition Indexes.cpp:298
Keylet book(Book const &b)
The beginning of an order book.
Definition Indexes.cpp:269
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:403
Keylet permissionedDomain(AccountID const &account, SeqProxy const &seq) noexcept
Definition Indexes.cpp:609
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:275
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:5
@ telFAILED_PROCESSING
Definition TER.h:42
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:69
@ terNO_LINE
Definition TER.h:220
@ terNO_AUTH
Definition TER.h:219
@ terNO_ACCOUNT
Definition TER.h:218
TER offerDelete(ApplyView &view, SLE::Ref sle, beast::Journal j)
Delete an offer.
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp, ExpiryComparison comparison=ExpiryComparison::Inclusive)
Determines whether the given expiration time has passed.
Definition View.cpp:51
bool isXRP(AccountID const &c)
Definition AccountID.h:84
TAmounts< STAmount, STAmount > Amounts
Definition Quality.h:69
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
@ tefINTERNAL
Definition TER.h:168
bool isLegalNet(STAmount const &value)
Definition STAmount.h:616
void increaseOwnerCount(ApplyView &view, SLE::Ref accountSle, SLE::Ref sponsorSle, std::uint32_t count, beast::Journal j)
Increase owner-count fields when the caller supplies the sponsor.
std::string transToken(TER code)
Definition TER.cpp:257
Currency const & xrpCurrency()
XRP currency.
Definition UintTypes.cpp:99
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
TER canTrade(ReadView const &view, Asset const &asset, std::uint8_t depth=0)
Check whether asset may be traded on the DEX.
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:614
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:103
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
TER verifyValidDomain(ApplyView &view, AccountID const &account, UInt256 domainID, beast::Journal j)
STAmount multiplyRound(STAmount const &amount, Rate const &rate, bool roundUp)
Definition Rate2.cpp:45
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition STAmount.cpp:423
TER checkGlobalFrozen(ReadView const &view, Asset const &asset)
ApplyFlags
Definition ApplyView.h:27
@ TapRetry
Definition ApplyView.h:36
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
STAmount mulRound(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
@ temBAD_ISSUER
Definition TER.h:81
@ temBAD_CURRENCY
Definition TER.h:78
@ temBAD_EXPIRATION
Definition TER.h:79
@ temBAD_SEQUENCE
Definition TER.h:92
@ temINVALID_FLAG
Definition TER.h:99
@ temMALFORMED
Definition TER.h:75
@ temBAD_AMOUNT
Definition TER.h:77
@ temBAD_OFFER
Definition TER.h:83
@ temREDUNDANT
Definition TER.h:100
bool isTesSuccess(TER x) noexcept
Definition TER.h:683
STAmount divRoundStrict(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
TERSubset< CanCvtToTER > TER
Definition TER.h:654
XRPAmount accountReserve(ReadView const &view, SLE::ConstRef sle, beast::Journal j, Adjustment adj={})
Returns the account reserve, in drops.
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, std::uint8_t depth=0)
Check if the account lacks required authorization for MPT.
@ tecINSUF_RESERVE_OFFER
Definition TER.h:297
@ tecDIR_FULL
Definition TER.h:295
@ tecNO_AUTH
Definition TER.h:308
@ tecINTERNAL
Definition TER.h:318
@ tecFAILED_PROCESSING
Definition TER.h:294
@ tecFROZEN
Definition TER.h:311
@ tecUNFUNDED_OFFER
Definition TER.h:292
@ tecEXPIRED
Definition TER.h:322
@ tecNO_LINE
Definition TER.h:309
@ tecKILLED
Definition TER.h:324
@ tecNO_PERMISSION
Definition TER.h:313
@ tecNO_ISSUER
Definition TER.h:307
STAmount multiply(STAmount const &amount, Number const &frac, Number::RoundingMode rm)
bool isTecClaim(TER x) noexcept
Definition TER.h:690
BadAsset const & badAsset()
Definition Asset.h:40
OfferCrossing
Definition Steps.h:38
std::function< void(SLE::Ref)> describeOwnerDir(AccountID const &account)
Returns a function that sets the owner on a directory SLE.
constexpr std::uint64_t kMaxMpTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:297
STAmount divideRound(STAmount const &amount, Rate const &rate, bool roundUp)
Definition Rate2.cpp:80
@ tesSUCCESS
Definition TER.h:250
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:92
ReadView const & view
Definition Transactor.h:95
std::reference_wrapper< ServiceRegistry > registry
Definition Transactor.h:94
beast::Journal const j
Definition Transactor.h:100
State information when preflighting a tx.
Definition Transactor.h:39
beast::Journal const j
Definition Transactor.h:46
Represents a transfer rate.
Definition Rate.h:21
std::uint32_t value
Definition Rate.h:22
T tie(T... args)
T what(T... args)