xrpld
Loading...
Searching...
No Matches
Payment.cpp
1#include <xrpl/tx/transactors/payment/Payment.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/beast/utility/Zero.h>
5#include <xrpl/beast/utility/instrumentation.h>
6#include <xrpl/core/ServiceRegistry.h>
7#include <xrpl/ledger/PaymentSandbox.h>
8#include <xrpl/ledger/ReadView.h>
9#include <xrpl/ledger/helpers/AccountRootHelpers.h>
10#include <xrpl/ledger/helpers/CredentialHelpers.h>
11#include <xrpl/ledger/helpers/MPTokenHelpers.h>
12#include <xrpl/ledger/helpers/PermissionedDEXHelpers.h>
13#include <xrpl/ledger/helpers/SponsorHelpers.h>
14#include <xrpl/ledger/helpers/TokenHelpers.h>
15#include <xrpl/protocol/AccountID.h>
16#include <xrpl/protocol/Asset.h>
17#include <xrpl/protocol/Feature.h>
18#include <xrpl/protocol/Indexes.h>
19#include <xrpl/protocol/Issue.h>
20#include <xrpl/protocol/LedgerFormats.h>
21#include <xrpl/protocol/MPTIssue.h>
22#include <xrpl/protocol/Permissions.h>
23#include <xrpl/protocol/Quality.h>
24#include <xrpl/protocol/Rate.h>
25#include <xrpl/protocol/SField.h>
26#include <xrpl/protocol/STAmount.h>
27#include <xrpl/protocol/STLedgerEntry.h>
28#include <xrpl/protocol/STPathSet.h>
29#include <xrpl/protocol/STTx.h>
30#include <xrpl/protocol/TER.h>
31#include <xrpl/protocol/TxFlags.h>
32#include <xrpl/protocol/UintTypes.h>
33#include <xrpl/protocol/XRPAmount.h>
34#include <xrpl/protocol/jss.h>
35#include <xrpl/tx/Transactor.h>
36#include <xrpl/tx/applySteps.h>
37#include <xrpl/tx/paths/RippleCalc.h>
38
39#include <algorithm>
40#include <cstdint>
41#include <limits>
42#include <memory>
43#include <optional>
44#include <unordered_set>
45
46namespace xrpl {
47
50{
51 auto calculateMaxXRPSpend = [](STTx const& tx) -> XRPAmount {
52 STAmount const maxAmount = tx.isFieldPresent(sfSendMax) ? tx[sfSendMax] : tx[sfAmount];
53
54 // If there's no sfSendMax in XRP, and the sfAmount isn't
55 // in XRP, then the transaction does not spend XRP.
56 return maxAmount.native() ? maxAmount.xrp() : beast::kZero;
57 };
58
59 return TxConsequences{ctx.tx, calculateMaxXRPSpend(ctx.tx)};
60}
61
64 AccountID const& account,
65 STAmount const& dstAmount,
66 std::optional<STAmount> const& sendMax)
67{
68 if (sendMax)
69 {
70 return *sendMax;
71 }
72 return dstAmount.asset().visit(
73 [&](MPTIssue const& issue) { return dstAmount; },
74 [&](Issue const& issue) {
75 if (issue.native())
76 return dstAmount;
77 return STAmount(
78 Issue{issue.currency, account},
79 dstAmount.mantissa(),
80 dstAmount.exponent(),
81 dstAmount < beast::kZero);
82 });
83}
84
85bool
87{
88 if (ctx.tx.isFieldPresent(sfCredentialIDs) && !ctx.rules.enabled(featureCredentials))
89 return false;
90 if (ctx.tx.isFieldPresent(sfDomainID) && !ctx.rules.enabled(featurePermissionedDEX))
91 return false;
92
93 return true;
94}
95
98{
99 auto& tx = ctx.tx;
100
101 STAmount const dstAmount(tx.getFieldAmount(sfAmount));
102 bool const isDstMPT = dstAmount.holds<MPTIssue>();
103 bool const mpTokensV2 = ctx.rules.enabled(featureMPTokensV2);
104
105 static constexpr std::uint32_t kTfMptPaymentMaskV1 = ~(tfUniversal | tfPartialPayment);
106 std::uint32_t const paymentMask =
107 (isDstMPT && !mpTokensV2) ? kTfMptPaymentMaskV1 : tfPaymentMask;
108
109 return paymentMask;
110}
111
112NotTEC
114{
115 auto& tx = ctx.tx;
116 auto& j = ctx.j;
117
118 STAmount const dstAmount(tx.getFieldAmount(sfAmount));
119 bool const isDstMPT = dstAmount.holds<MPTIssue>();
120 bool const mpTokensV2 = ctx.rules.enabled(featureMPTokensV2);
121
122 if (!ctx.rules.enabled(featureMPTokensV1) && isDstMPT)
123 return temDISABLED;
124
125 if (tx.isFlag(tfSponsorCreatedAccount))
126 {
127 if (!ctx.rules.enabled(featureSponsor))
128 return temDISABLED;
129
130 if (tx.isFlag(tfNoRippleDirect) || tx.isFlag(tfPartialPayment) || tx.isFlag(tfLimitQuality))
131 return temINVALID_FLAG;
132
133 if (tx.isFieldPresent(sfSendMax) || tx.isFieldPresent(sfPaths))
134 return temINVALID;
135
136 if (!dstAmount.native())
137 return temBAD_AMOUNT;
138 }
139
140 if (!mpTokensV2 && isDstMPT && ctx.tx.isFieldPresent(sfPaths))
141 return temMALFORMED;
142
143 // A zero DomainID is invalid for a PermissionedDomain ledger entry because
144 // keylet::permissionedDomain(UInt256) uses the DomainID as the ledger key.
145 if (auto const domainID = tx[~sfDomainID];
146 ctx.rules.enabled(fixCleanup3_2_0) && domainID && *domainID == beast::kZero)
147 return temMALFORMED;
148
149 bool const partialPaymentAllowed = tx.isFlag(tfPartialPayment);
150 bool const limitQuality = tx.isFlag(tfLimitQuality);
151 bool const defaultPathsAllowed = !tx.isFlag(tfNoRippleDirect);
152 bool const hasPaths = tx.isFieldPresent(sfPaths);
153 bool const hasMax = tx.isFieldPresent(sfSendMax);
154
155 auto const deliverMin = tx[~sfDeliverMin];
156
157 auto const account = tx.getAccountID(sfAccount);
158 STAmount const maxSourceAmount = getMaxSourceAmount(account, dstAmount, tx[~sfSendMax]);
159
160 if (!mpTokensV2 &&
161 ((isDstMPT && dstAmount.asset() != maxSourceAmount.asset()) ||
162 (!isDstMPT && maxSourceAmount.holds<MPTIssue>())))
163 {
164 JLOG(j.trace()) << "Malformed transaction: inconsistent issues: " << dstAmount.getFullText()
165 << " " << maxSourceAmount.getFullText() << " "
166 << deliverMin.value_or(STAmount{}).getFullText();
167 return temMALFORMED;
168 }
169
170 auto const& srcAsset = maxSourceAmount.asset();
171 auto const& dstAsset = dstAmount.asset();
172
173 bool const xrpDirect = srcAsset.native() && dstAsset.native();
174
175 if (!isLegalNet(dstAmount) || !isLegalNet(maxSourceAmount))
176 return temBAD_AMOUNT;
177
178 auto const dstAccountID = tx.getAccountID(sfDestination);
179
180 if (!dstAccountID)
181 {
182 JLOG(j.trace()) << "Malformed transaction: "
183 << "Payment destination account not specified.";
184 return temDST_NEEDED;
185 }
186 if (hasMax && maxSourceAmount <= beast::kZero)
187 {
188 JLOG(j.trace()) << "Malformed transaction: bad max amount: "
189 << maxSourceAmount.getFullText();
190 return temBAD_AMOUNT;
191 }
192 if (dstAmount <= beast::kZero)
193 {
194 JLOG(j.trace()) << "Malformed transaction: bad dst amount: " << dstAmount.getFullText();
195 return temBAD_AMOUNT;
196 }
197 auto bad = [&](auto const& asset) {
198 if (ctx.rules.enabled(featureMPTokensV2))
199 return badAsset() == asset;
200 return badCurrency() == asset;
201 };
202 if (bad(srcAsset) || bad(dstAsset))
203 {
204 JLOG(j.trace()) << "Malformed transaction: Bad currency.";
205 return temBAD_CURRENCY;
206 }
207 if (account == dstAccountID && equalTokens(srcAsset, dstAsset) && !hasPaths)
208 {
209 // You're signing yourself a payment.
210 // If hasPaths is true, you might be trying some arbitrage.
211 JLOG(j.trace()) << "Malformed transaction: "
212 << "Redundant payment from " << to_string(account)
213 << " to self without path for " << to_string(dstAsset);
214 return temREDUNDANT;
215 }
216 if (xrpDirect && hasMax)
217 {
218 // Consistent but redundant transaction.
219 JLOG(j.trace()) << "Malformed transaction: "
220 << "SendMax specified for XRP to XRP.";
221 return temBAD_SEND_XRP_MAX;
222 }
223 if ((xrpDirect || (!mpTokensV2 && isDstMPT)) && hasPaths)
224 {
225 // XRP is sent without paths.
226 JLOG(j.trace()) << "Malformed transaction: "
227 << "Paths specified for XRP to XRP or MPT to MPT.";
229 }
230 if (xrpDirect && partialPaymentAllowed)
231 {
232 // Consistent but redundant transaction.
233 JLOG(j.trace()) << "Malformed transaction: "
234 << "Partial payment specified for XRP to XRP.";
236 }
237 if ((xrpDirect || (!mpTokensV2 && isDstMPT)) && limitQuality)
238 {
239 // Consistent but redundant transaction.
240 JLOG(j.trace()) << "Malformed transaction: "
241 << "Limit quality specified for XRP to XRP or MPT to MPT.";
243 }
244 if ((xrpDirect || (!mpTokensV2 && isDstMPT)) && !defaultPathsAllowed)
245 {
246 // Consistent but redundant transaction.
247 JLOG(j.trace()) << "Malformed transaction: "
248 << "No ripple direct specified for XRP to XRP or MPT to MPT.";
250 }
251
252 if (deliverMin)
253 {
254 if (!partialPaymentAllowed)
255 {
256 JLOG(j.trace()) << "Malformed transaction: Partial payment not "
257 "specified for "
258 << jss::DeliverMin.cStr() << ".";
259 return temBAD_AMOUNT;
260 }
261
262 auto const dMin = *deliverMin;
263 if (!isLegalNet(dMin) || dMin <= beast::kZero)
264 {
265 JLOG(j.trace()) << "Malformed transaction: Invalid " << jss::DeliverMin.cStr()
266 << " amount. " << dMin.getFullText();
267 return temBAD_AMOUNT;
268 }
269 if (dMin.asset() != dstAmount.asset())
270 {
271 JLOG(j.trace()) << "Malformed transaction: Dst issue differs "
272 "from "
273 << jss::DeliverMin.cStr() << ". " << dMin.getFullText();
274 return temBAD_AMOUNT;
275 }
276 if (dMin > dstAmount)
277 {
278 JLOG(j.trace()) << "Malformed transaction: Dst amount less than "
279 << jss::DeliverMin.cStr() << ". " << dMin.getFullText();
280 return temBAD_AMOUNT;
281 }
282 }
283
284 if (auto const err = credentials::checkFields(ctx.tx, ctx.rules, ctx.j); !isTesSuccess(err))
285 return err;
286
287 return tesSUCCESS;
288}
289
290NotTEC
292 ReadView const& view,
293 STTx const& tx,
294 std::unordered_set<GranularPermissionType> const& heldGranularPermissions)
295{
296 auto const& dstAmount = tx.getFieldAmount(sfAmount);
297 auto const& amountAsset = dstAmount.asset();
298
299 // Granular permissions are only valid for direct payments.
300 if (tx.isFieldPresent(sfSendMax) && tx[sfSendMax].asset() != amountAsset)
302
303 if (isXRP(amountAsset))
305
306 return amountAsset.visit(
307 [&](MPTIssue const& mptIssue) -> NotTEC {
308 // For MPT payments, the MPTokenIssuanceID encodes the issuer unambiguously,
309 // unlike IOU, there is no endpoint aliasing where either side of the
310 // trustline can appear as the issuer.
311 if (heldGranularPermissions.contains(PaymentMint) &&
312 mptIssue.getIssuer() == tx[sfAccount])
313 return tesSUCCESS;
314 if (heldGranularPermissions.contains(PaymentBurn) &&
315 mptIssue.getIssuer() == tx[sfDestination])
316 return tesSUCCESS;
318 },
319 [&](Issue const& issue) -> NotTEC {
320 // For IOU payments, either endpoint may be encoded as the issuer in
321 // sfAmount. PaySteps normalizes those endpoint aliases, so sfAmount.issuer
322 // alone does not reliably identify whether the transaction issues or redeems
323 // IOUs. We determine PaymentMint vs PaymentBurn from the trustline balance
324 // direction instead.
325 auto const account = tx[sfAccount];
326 auto const destination = tx[sfDestination];
327
328 // Reject if neither endpoint is the issuer.
329 if (issue.getIssuer() != account && issue.getIssuer() != destination)
331
332 auto const sle = view.read(keylet::trustLine(account, destination, issue.currency));
333 if (!sle)
335
336 bool const accountIsLow = (account < destination);
337 auto const destLimit = sle->getFieldAmount(accountIsLow ? sfHighLimit : sfLowLimit);
338 auto const rawBalance = sle->getFieldAmount(sfBalance);
339 bool const accountIsHolder =
340 accountIsLow ? rawBalance > beast::kZero : rawBalance < beast::kZero;
341
342 bool const mayIssue =
343 heldGranularPermissions.contains(PaymentMint) && destLimit > beast::kZero;
344
345 // PaymentMint requires the destination to be the holder and the account to be the
346 // issuer. destLimit > 0: destination is willing to hold account's IOUs (account is the
347 // issuer). !accountIsHolder: DirectStepI will issue, not redeem.
348 if (mayIssue && !accountIsHolder)
349 return tesSUCCESS;
350
351 // PaymentBurn requires the source account to be the holder and the destination to be
352 // the issuer. accountIsHolder: DirectStepI will redeem, not issue.
353 if (heldGranularPermissions.contains(PaymentBurn) && accountIsHolder)
354 {
355 if (view.rules().enabled(fixCleanup3_4_0))
356 {
357 // Redeeming stops at the balance held; beyond that the payment engine
358 // crosses zero and issues the account's own IOUs, which is a mint. So with
359 // only PaymentBurn we must check the amount against the balance held. The
360 // granular template forbids sfPaths, tfPartialPayment and a cross-asset
361 // sfSendMax, so this is a single direct step, sfAmount is what the
362 // trustline is debited.
363 STAmount const held = accountIsLow ? rawBalance : -rawBalance;
364 if (dstAmount <= held || mayIssue)
365 return tesSUCCESS;
366 }
367 else
368 {
369 return tesSUCCESS;
370 }
371 }
372
374 });
375}
376
377TER
379{
380 // Ripple if source or destination is non-native or if there are paths.
381 bool const partialPaymentAllowed = ctx.tx.isFlag(tfPartialPayment);
382 auto const hasPaths = ctx.tx.isFieldPresent(sfPaths);
383 auto const sendMax = ctx.tx[~sfSendMax];
384
385 AccountID const dstAccountID(ctx.tx[sfDestination]);
386 STAmount const dstAmount(ctx.tx[sfAmount]);
387
388 auto const k = keylet::account(dstAccountID);
389 auto const sleDst = ctx.view.read(k);
390
391 if (!sleDst)
392 {
393 // Destination account does not exist.
394 if (!dstAmount.native())
395 {
396 JLOG(ctx.j.trace()) << "Delay transaction: Destination account does not exist.";
397
398 // Another transaction could create the account and then this
399 // transaction would succeed.
400 return tecNO_DST;
401 }
402 // A partial payment may not fund a new account.
403 if (partialPaymentAllowed)
404 {
405 // Open view: the soft tel (unchanged).
406 if (ctx.view.open())
407 {
408 // Make retry work smaller, by rejecting this.
409 JLOG(ctx.j.trace()) << "Delay transaction: Partial payment not "
410 "allowed to create account.";
411 return telNO_DST_PARTIAL;
412 }
413 // Inner batch txns are claimed on a closed view, where a tel is
414 // invalid, so use the tef.
415 if (ctx.parentBatchId && ctx.view.rules().enabled(featureBatchV1_1))
416 return tefNO_DST_PARTIAL;
417 }
418 if (dstAmount < STAmount(ctx.view.fees().reserve))
419 {
420 // accountReserve is the minimum amount that an account can have.
421 // Reserve is not scaled by load.
422 if (!ctx.tx.isFlag(tfSponsorCreatedAccount))
423 {
424 // The minimum amount when creating a Sponsored Account is 1 drop.
425 // Since the reserve is covered by the sponsor, you don't need to hold the
426 // 1-increment reserve yourself.
427 JLOG(ctx.j.trace()) << "Delay transaction: Destination account does not exist. "
428 << "Insufficient payment to create account.";
429
430 // TODO: de-dupe
431 // Another transaction could create the account and then this
432 // transaction would succeed.
433 return tecNO_DST_INSUF_XRP;
434 }
435 }
436 }
437 else if (ctx.tx.isFlag(tfSponsorCreatedAccount))
438 {
439 // The tfSponsorCreatedAccount flag is specific to account creation via
440 // sponsorship. If the destination account already exists, applying this
441 // flag is invalid.
443 }
444 else if (sleDst->isFlag(lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag))
445 {
446 // The tag is basically account-specific information we don't
447 // understand, but we can require someone to fill it in.
448
449 // We didn't make this test for a newly-formed account because there's
450 // no way for this field to be set.
451 JLOG(ctx.j.trace()) << "Malformed transaction: DestinationTag required.";
452
453 return tecDST_TAG_NEEDED;
454 }
455
456 // Payment with at least one intermediate step and uses transitive balances.
457 if (hasPaths || sendMax || !dstAmount.native())
458 {
459 STPathSet const& paths = ctx.tx.getFieldPathSet(sfPaths);
460
461 if (paths.size() > kMaxPathSize || std::ranges::any_of(paths, [](STPath const& path) {
462 return path.size() > kMaxPathLength;
463 }))
464 {
465 // Open view: the soft tel (unchanged). Inner batch txns are claimed
466 // on a closed view, where a tel is invalid, so use the tef.
467 if (ctx.view.open())
468 return telBAD_PATH_COUNT;
469 if (ctx.parentBatchId && ctx.view.rules().enabled(featureBatchV1_1))
470 return tefBAD_PATH_COUNT;
471 }
472 }
473
474 if (auto const err = credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j);
475 !isTesSuccess(err))
476 return err;
477
478 if (ctx.tx.isFieldPresent(sfDomainID))
479 {
480 if (ctx.view.rules().enabled(fixCleanup3_4_0))
481 {
482 auto const domainID = ctx.tx[sfDomainID];
483 auto const sleDomain = ctx.view.read(keylet::permissionedDomain(domainID));
484 if (!sleDomain)
485 return tecNO_PERMISSION;
486
487 // Domain owner is always considered in the domain. For other accounts,
488 // suppress tecEXPIRED so doApply can run and delete expired credential
489 // SLEs from the ledger.
490 auto const checkAccount = [&](AccountID const& acct) -> TER {
491 if (sleDomain->getAccountID(sfOwner) == acct)
492 return tesSUCCESS;
493 // validDomain returns tecNO_AUTH when no matching credential is
494 // found. Map it to tecNO_PERMISSION to preserve existing behavior.
495 if (auto const err = credentials::validDomain(ctx.view, domainID, acct);
496 !isTesSuccess(err) && err != tecEXPIRED)
497 return tecNO_PERMISSION;
498 return tesSUCCESS;
499 };
500
501 if (auto const err = checkAccount(ctx.tx[sfAccount]); !isTesSuccess(err))
502 return err;
503 if (auto const err = checkAccount(ctx.tx[sfDestination]); !isTesSuccess(err))
504 return err;
505 }
506 else
507 {
508 if (!permissioned_dex::accountInDomain(ctx.view, ctx.tx[sfAccount], ctx.tx[sfDomainID]))
509 return tecNO_PERMISSION;
510
512 ctx.view, ctx.tx[sfDestination], ctx.tx[sfDomainID]))
513 return tecNO_PERMISSION;
514 }
515 }
516
517 return tesSUCCESS;
518}
519
520TER
522{
523 // If a DomainID is present, verify both sender and destination are still in
524 // the domain and delete any expired credential SLEs from the ledger.
525 if (ctx_.tx.isFieldPresent(sfDomainID) && ctx_.view().rules().enabled(fixCleanup3_4_0))
526 {
527 auto const domainID = ctx_.tx[sfDomainID];
528 auto const sleDomain = ctx_.view().read(keylet::permissionedDomain(domainID));
529 if (!sleDomain)
530 return tecINTERNAL; // LCOV_EXCL_LINE
531
532 auto const cleanupFor = [&](AccountID const& acct) -> TER {
533 if (sleDomain->getAccountID(sfOwner) == acct)
534 return tesSUCCESS;
535 return verifyValidDomain(ctx_.view(), acct, domainID, j_);
536 };
537
538 auto const destination = ctx_.tx[sfDestination];
539 auto const senderErr = cleanupFor(accountID_);
540 auto const destinationErr = accountID_ == destination ? senderErr : cleanupFor(destination);
541
542 if (!isTesSuccess(senderErr))
543 return senderErr;
544 if (!isTesSuccess(destinationErr))
545 return destinationErr;
546 }
547
548 auto const deliverMin = ctx_.tx[~sfDeliverMin];
549
550 // Ripple if source or destination is non-native or if there are paths.
551 bool const partialPaymentAllowed = ctx_.tx.isFlag(tfPartialPayment);
552 bool const limitQuality = ctx_.tx.isFlag(tfLimitQuality);
553 bool const defaultPathsAllowed = !ctx_.tx.isFlag(tfNoRippleDirect);
554 auto const hasPaths = ctx_.tx.isFieldPresent(sfPaths);
555 auto const sendMax = ctx_.tx[~sfSendMax];
556
557 AccountID const dstAccountID(ctx_.tx.getAccountID(sfDestination));
558 STAmount const dstAmount(ctx_.tx.getFieldAmount(sfAmount));
559 bool const isDstMPT = dstAmount.holds<MPTIssue>();
560 STAmount const maxSourceAmount = getMaxSourceAmount(accountID_, dstAmount, sendMax);
561
562 JLOG(j_.trace()) << "maxSourceAmount=" << maxSourceAmount.getFullText()
563 << " dstAmount=" << dstAmount.getFullText();
564
565 // Open a ledger for editing.
566 auto const k = keylet::account(dstAccountID);
567 SLE::pointer sleDst = view().peek(k);
568
569 if (!sleDst)
570 {
571 // Create the account.
572 sleDst = std::make_shared<SLE>(k);
573 sleDst->setAccountID(sfAccount, dstAccountID);
574 sleDst->setFieldU32(sfSequence, view().seq());
575 sleDst->setFieldAmount(sfBalance, XRPAmount(beast::kZero));
576
577 if (ctx_.tx.isFlag(tfSponsorCreatedAccount))
578 {
579 auto const sponsor = view().peek(keylet::account(accountID_));
580 if (!sponsor)
581 return tefINTERNAL; // LCOV_EXCL_LINE
582 auto const currentSponsoringAccountCount =
583 sponsor->getFieldU32(sfSponsoringAccountCount);
584 if (currentSponsoringAccountCount == std::numeric_limits<std::uint32_t>::max())
585 {
586 // LCOV_EXCL_START
587 JLOG(j_.fatal()) << "Sponsoring account count overflow for account "
589 return tecINTERNAL;
590 // LCOV_EXCL_STOP
591 }
592 sponsor->setFieldU32(sfSponsoringAccountCount, currentSponsoringAccountCount + 1);
593
594 addSponsorToLedgerEntry(sleDst, sponsor);
595 view().update(sponsor);
596 }
597
598 view().insert(sleDst);
599 }
600 else
601 {
602 // Tell the engine that we are intending to change the destination
603 // account. The source account gets always charged a fee so it's always
604 // marked as modified.
605 view().update(sleDst);
606 }
607
608 bool const mpTokensV2 = view().rules().enabled(featureMPTokensV2);
609
610 // Direct MPT payment is handled by payment engine if MPTokensV2 is enabled
611 bool const ripple = (hasPaths || sendMax || !dstAmount.native()) && (!isDstMPT || mpTokensV2);
612
613 if (ripple)
614 {
615 // XRPL payment with at least one intermediate step and uses
616 // transitive balances.
617
618 // An account that requires authorization has two ways to get an
619 // IOU Payment in:
620 // 1. If Account == Destination, or
621 // 2. If Account is deposit preauthorized by destination.
622
623 if (auto err = verifyDepositPreauth(
624 ctx_.tx, ctx_.view(), accountID_, dstAccountID, sleDst, ctx_.journal);
625 !isTesSuccess(err))
626 return err;
627
629 rcInput.partialPaymentAllowed = partialPaymentAllowed;
630 rcInput.defaultPathsAllowed = defaultPathsAllowed;
631 rcInput.limitQuality = limitQuality;
632 rcInput.isLedgerOpen = view().open();
633
635 {
636 PaymentSandbox pv(&view());
637 JLOG(j_.debug()) << "Entering RippleCalc in payment: " << ctx_.tx.getTransactionID();
639 pv,
640 maxSourceAmount,
641 dstAmount,
642 dstAccountID,
644 ctx_.tx.getFieldPathSet(sfPaths),
645 ctx_.tx[~sfDomainID],
646 ctx_.registry,
647 &rcInput);
648 // VFALCO NOTE We might not need to apply, depending
649 // on the TER. But always applying *should*
650 // be safe.
651 pv.apply(ctx_.rawView());
652 }
653
654 // TODO: is this right? If the amount is the correct amount, was
655 // the delivered amount previously set?
656 if (isTesSuccess(rc.result()) && rc.actualAmountOut != dstAmount)
657 {
658 if (deliverMin && rc.actualAmountOut < *deliverMin)
659 {
661 }
662 else
663 {
664 ctx_.deliver(rc.actualAmountOut);
665 }
666 }
667
668 auto terResult = rc.result();
669
670 // Because of its overhead, if RippleCalc
671 // fails with a retry code, claim a fee
672 // instead. Maybe the user will be more
673 // careful with their path spec next time.
674 if (isTerRetry(terResult))
675 terResult = tecPATH_DRY;
676 return terResult;
677 }
678 if (isDstMPT)
679 {
680 JLOG(j_.trace()) << " dstAmount=" << dstAmount.getFullText();
681 auto const& mptIssue = dstAmount.get<MPTIssue>();
682
683 if (auto const ter = requireAuth(view(), mptIssue, accountID_); !isTesSuccess(ter))
684 return ter;
685
686 if (auto const ter = requireAuth(view(), mptIssue, dstAccountID); !isTesSuccess(ter))
687 return ter;
688
689 if (auto const ter = canTransfer(view(), mptIssue, accountID_, dstAccountID);
690 !isTesSuccess(ter))
691 return ter;
692
693 if (auto err = verifyDepositPreauth(
694 ctx_.tx, ctx_.view(), accountID_, dstAccountID, sleDst, ctx_.journal);
695 !isTesSuccess(err))
696 return err;
697
698 auto const& issuer = mptIssue.getIssuer();
699
700 // Transfer rate
701 Rate rate{QUALITY_ONE};
702 // Payment between the holders
703 if (accountID_ != issuer && dstAccountID != issuer)
704 {
705 // If globally/individually locked then
706 // - can't send between holders
707 // - holder can send back to issuer
708 // - issuer can send to holder
709 if (isAnyFrozen(view(), {accountID_, dstAccountID}, mptIssue))
710 return tecLOCKED;
711
712 // Get the rate for a payment between the holders.
713 rate = transferRate(view(), mptIssue.getMptID());
714 }
715
716 // Amount to deliver.
717 STAmount amountDeliver = dstAmount;
718 // Factor in the transfer rate.
719 // No rounding. It'll change once MPT integrated into DEX.
720 STAmount requiredMaxSourceAmount = multiply(dstAmount, rate);
721
722 // Send more than the account wants to pay or less than
723 // the account wants to deliver (if no SendMax).
724 // Adjust the amount to deliver.
725 if (partialPaymentAllowed && requiredMaxSourceAmount > maxSourceAmount)
726 {
727 requiredMaxSourceAmount = maxSourceAmount;
728 // No rounding. It'll change once MPT integrated into DEX.
729 amountDeliver = divide(maxSourceAmount, rate);
730 }
731
732 if (requiredMaxSourceAmount > maxSourceAmount ||
733 (deliverMin && amountDeliver < *deliverMin))
734 return tecPATH_PARTIAL;
735
736 PaymentSandbox pv(&view());
737 auto res = accountSend(pv, accountID_, dstAccountID, amountDeliver, ctx_.journal);
738 if (isTesSuccess(res))
739 {
740 pv.apply(ctx_.rawView());
741
742 // If the actual amount delivered is different from the original
743 // amount due to partial payment or transfer fee, we need to update
744 // DeliveredAmount using the actual delivered amount
745 if (view().rules().enabled(fixMPTDeliveredAmount) && amountDeliver != dstAmount)
746 ctx_.deliver(amountDeliver);
747 }
748 else if (res == tecINSUFFICIENT_FUNDS || res == tecPATH_DRY)
749 {
750 res = tecPATH_PARTIAL;
751 }
752
753 return res;
754 }
755
756 XRPL_ASSERT(dstAmount.native(), "xrpl::Payment::doApply : amount is XRP");
757
758 // Direct XRP payment.
759
760 auto const sleSrc = view().peek(keylet::account(accountID_));
761 if (!sleSrc)
762 return tefINTERNAL; // LCOV_EXCL_LINE
763
764 // the number of reserves in this ledger for this account that require a
765 // reserve.
766 auto const reserve = accountReserve(view(), sleSrc, j_);
767
768 // In a delegated / fee sponsored payment, the fee payer is not the source account (accountID_).
769 bool const accountIsPayer = ctx_.tx.getFeePayerID() == accountID_;
770
771 // preFeeBalance_ is the balance on the source account (accountID_) BEFORE the fees
772 // were charged. If source account is the fee payer, it must also cover the fee.
773 // The final spend may use the reserve to cover fees.
774 auto const minRequiredFunds =
775 accountIsPayer ? std::max(reserve, ctx_.tx.getFieldAmount(sfFee).xrp()) : reserve;
776
777 if (preFeeBalance_ < dstAmount.xrp() + minRequiredFunds)
778 {
779 // Vote no. However the transaction might succeed, if applied in
780 // a different order.
781 JLOG(j_.trace()) << "Delay transaction: Insufficient funds: " << to_string(preFeeBalance_)
782 << " / " << to_string(dstAmount.xrp() + minRequiredFunds) << " ("
783 << to_string(reserve) << ")";
784
785 return tecUNFUNDED_PAYMENT;
786 }
787
788 // Pseudo-accounts cannot receive payments, other than these native to
789 // their underlying ledger object - implemented in their respective
790 // transaction types. Note, this is not amendment-gated because all writes
791 // to pseudo-account discriminator fields **are** amendment gated, hence the
792 // behaviour of this check will always match the active amendments.
793 if (isPseudoAccount(sleDst))
794 return tecNO_PERMISSION;
795
796 // The source account does have enough money. Make sure the
797 // source account has authority to deposit to the destination.
798 // An account that requires authorization has three ways to get an XRP
799 // Payment in:
800 // 1. If Account == Destination, or
801 // 2. If Account is deposit preauthorized by destination, or
802 // 3. If the destination's XRP balance is
803 // a. less than or equal to the base reserve and
804 // b. the deposit amount is less than or equal to the base reserve,
805 // then we allow the deposit.
806 //
807 // Rule 3 is designed to keep an account from getting wedged
808 // in an unusable state if it sets the lsfDepositAuth flag and
809 // then consumes all of its XRP. Without the rule if an
810 // account with lsfDepositAuth set spent all of its XRP, it
811 // would be unable to acquire more XRP required to pay fees.
812 //
813 // We choose the base reserve as our bound because it is
814 // a small number that seldom changes but is always sufficient
815 // to get the account un-wedged.
816
817 // Get the base reserve.
818 XRPAmount const dstReserve{view().fees().reserve};
819
820 if (dstAmount > dstReserve || sleDst->getFieldAmount(sfBalance) > dstReserve)
821 {
822 if (auto err = verifyDepositPreauth(
823 ctx_.tx, ctx_.view(), accountID_, dstAccountID, sleDst, ctx_.journal);
824 !isTesSuccess(err))
825 return err;
826 }
827
828 // Do the arithmetic for the transfer and make the ledger change.
829 sleSrc->setFieldAmount(sfBalance, sleSrc->getFieldAmount(sfBalance) - dstAmount);
830 sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(sfBalance) + dstAmount);
831
832 // Re-arm the password change fee if we can and need to.
833 if (sleDst->isFlag(lsfPasswordSpent))
834 sleDst->clearFlag(lsfPasswordSpent);
835
836 return tesSUCCESS;
837}
838
839void
841{
842 // No transaction-specific invariants yet (future work).
843}
844
845bool
847{
848 // No transaction-specific invariants yet (future work).
849 return true;
850}
851
852} // namespace xrpl
T any_of(T... args)
A generic endpoint for log messages.
Definition Journal.h:44
Stream trace() const
Severity stream access functions.
Definition Journal.h:338
virtual SLE::pointer peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void insert(SLE::Ref sle)=0
Insert a new state SLE.
virtual void update(SLE::Ref sle)=0
Indicate changes to a peeked SLE.
constexpr auto visit(Visitors &&... visitors) const -> decltype(auto)
Definition Asset.h:117
constexpr bool native() const
Definition Asset.h:125
A currency issued by an account.
Definition Issue.h:18
static bool native()
Definition MPTIssue.h:61
AccountID const & getIssuer() const
Definition MPTIssue.cpp:29
A wrapper which makes credits unavailable to balances.
void apply(RawView &to)
Apply changes to base view.
void visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override
Inspect a single ledger entry modified by this transaction.
Definition Payment.cpp:840
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
Definition Payment.cpp:49
static NotTEC preflight(PreflightContext const &ctx)
Definition Payment.cpp:113
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
Definition Payment.cpp:97
static bool checkExtraFeatures(PreflightContext const &ctx)
Definition Payment.cpp:86
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.
Definition Payment.cpp:846
static std::size_t const kMaxPathSize
TER doApply() override
Definition Payment.cpp:521
static NotTEC checkGranularSemantics(ReadView const &view, STTx const &tx, std::unordered_set< GranularPermissionType > const &heldGranularPermissions)
Definition Payment.cpp:291
static TER preclaim(PreclaimContext const &ctx)
Definition Payment.cpp:378
A view into a ledger.
Definition ReadView.h:41
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
virtual bool open() const =0
Returns true if this reflects an open ledger.
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
constexpr TIss const & get() const
std::string getFullText() const override
Definition STAmount.cpp:637
std::uint64_t mantissa() const noexcept
Definition STAmount.h:490
bool native() const noexcept
Definition STAmount.h:471
Asset const & asset() const
Definition STAmount.h:496
int exponent() const noexcept
Definition STAmount.h:459
XRPAmount xrp() const
Definition STAmount.cpp:272
std::shared_ptr< STLedgerEntry > pointer
std::shared_ptr< STLedgerEntry const > const & ConstRef
bool isFlag(std::uint32_t) const
Definition STObject.cpp:511
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:464
STPathSet const & getFieldPathSet(SField const &field) const
Definition STObject.cpp:664
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:657
std::vector< STPath >::size_type size() const
Definition STPathSet.h:624
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
static Output rippleCalculate(PaymentSandbox &view, STAmount const &saMaxAmountReq, STAmount const &saDstAmountReq, AccountID const &uDstAccountID, AccountID const &uSrcAccountID, STPathSet const &spsPaths, std::optional< UInt256 > const &domainID, ServiceRegistry &registry, Input const *const pInputs=nullptr)
T contains(T... args)
T make_shared(T... args)
T max(T... args)
constexpr Zero kZero
Definition Zero.h:30
NotTEC checkFields(STTx const &tx, Rules const &rules, beast::Journal j)
TER valid(STTx const &tx, ReadView const &view, AccountID const &src, beast::Journal j)
TER validDomain(ReadView const &view, UInt256 domainID, AccountID const &subject)
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
@ telBAD_PATH_COUNT
Definition TER.h:40
@ telNO_DST_PARTIAL
Definition TER.h:44
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:69
@ terNO_DELEGATE_PERMISSION
Definition TER.h:231
bool isTerRetry(TER x) noexcept
Definition TER.h:677
bool isXRP(AccountID const &c)
Definition AccountID.h:84
STAmount getMaxSourceAmount(AccountID const &account, STAmount const &dstAmount, std::optional< STAmount > const &sendMax)
Definition Payment.cpp:63
@ tefBAD_PATH_COUNT
Definition TER.h:184
@ tefNO_DST_PARTIAL
Definition TER.h:183
@ tefINTERNAL
Definition TER.h:168
bool isLegalNet(STAmount const &value)
Definition STAmount.h:616
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to, WaiveMPTCanTransfer waive=WaiveMPTCanTransfer::No, std::uint8_t depth=0)
Check whether to may receive the given MPT from from.
constexpr FlagValue tfUniversal
Definition TxFlags.h:45
void addSponsorToLedgerEntry(SLE::Ref sle, SLE::ConstRef sponsorSle, SF_ACCOUNT const &field=sfSponsor)
Stamp a reserve sponsor onto a ledger entry using an explicit sponsor SLE.
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:614
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, SLE::Ref sponsorSle={}, WaiveTransferFee waiveFee=WaiveTransferFee::No, AllowMPTOverflow allowOverflow=AllowMPTOverflow::No)
Calls static accountSendIOU if saAmount represents Issue.
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)
bool isPseudoAccount(SLE::const_pointer sleAcct)
Returns true if and only if sleAcct is a pseudo-account of any kind (i.e.
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
@ temBAD_SEND_XRP_PATHS
Definition TER.h:91
@ temBAD_CURRENCY
Definition TER.h:78
@ temBAD_SEND_XRP_MAX
Definition TER.h:88
@ temBAD_SEND_XRP_LIMIT
Definition TER.h:87
@ temINVALID
Definition TER.h:98
@ temINVALID_FLAG
Definition TER.h:99
@ temBAD_SEND_XRP_PARTIAL
Definition TER.h:90
@ temDST_NEEDED
Definition TER.h:97
@ temMALFORMED
Definition TER.h:75
@ temBAD_SEND_XRP_NO_DIRECT
Definition TER.h:89
@ temDISABLED
Definition TER.h:102
@ temBAD_AMOUNT
Definition TER.h:77
@ temREDUNDANT
Definition TER.h:100
bool isTesSuccess(TER x) noexcept
Definition TER.h:683
bool isAnyFrozen(ReadView const &view, std::initializer_list< AccountID > const &accounts, MPTIssue const &mptIssue, std::uint8_t depth=0)
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.
@ tecLOCKED
Definition TER.h:366
@ tecPATH_PARTIAL
Definition TER.h:290
@ tecUNFUNDED_PAYMENT
Definition TER.h:293
@ tecPATH_DRY
Definition TER.h:302
@ tecNO_DST_INSUF_XRP
Definition TER.h:299
@ tecNO_SPONSOR_PERMISSION
Definition TER.h:377
@ tecINTERNAL
Definition TER.h:318
@ tecINSUFFICIENT_FUNDS
Definition TER.h:333
@ tecEXPIRED
Definition TER.h:322
@ tecNO_PERMISSION
Definition TER.h:313
@ tecDST_TAG_NEEDED
Definition TER.h:317
@ tecNO_DST
Definition TER.h:298
STAmount multiply(STAmount const &amount, Number const &frac, Number::RoundingMode rm)
BadAsset const & badAsset()
Definition Asset.h:40
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, SLE::ConstRef sleDst, beast::Journal j)
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
@ tesSUCCESS
Definition TER.h:250
constexpr bool equalTokens(Asset const &lhs, Asset const &rhs)
Definition Asset.h:286
XRPAmount reserve
Minimum XRP an account must hold to exist on the ledger.
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::optional< UInt256 const > const parentBatchId
Definition Transactor.h:99
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
void setResult(TER const value)
Definition RippleCalc.h:68