1#include <xrpl/tx/transactors/payment/Payment.h>
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>
52 STAmount const maxAmount = tx.isFieldPresent(sfSendMax) ? tx[sfSendMax] : tx[sfAmount];
73 [&](
MPTIssue const& issue) {
return dstAmount; },
74 [&](
Issue const& issue) {
78 Issue{issue.currency, account},
101 STAmount const dstAmount(tx.getFieldAmount(sfAmount));
103 bool const mpTokensV2 = ctx.
rules.
enabled(featureMPTokensV2);
107 (isDstMPT && !mpTokensV2) ? kTfMptPaymentMaskV1 : tfPaymentMask;
118 STAmount const dstAmount(tx.getFieldAmount(sfAmount));
120 bool const mpTokensV2 = ctx.
rules.
enabled(featureMPTokensV2);
125 if (tx.isFlag(tfSponsorCreatedAccount))
130 if (tx.isFlag(tfNoRippleDirect) || tx.isFlag(tfPartialPayment) || tx.isFlag(tfLimitQuality))
133 if (tx.isFieldPresent(sfSendMax) || tx.isFieldPresent(sfPaths))
145 if (
auto const domainID = tx[~sfDomainID];
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);
155 auto const deliverMin = tx[~sfDeliverMin];
157 auto const account = tx.getAccountID(sfAccount);
161 ((isDstMPT && dstAmount.
asset() != maxSourceAmount.
asset()) ||
164 JLOG(j.trace()) <<
"Malformed transaction: inconsistent issues: " << dstAmount.
getFullText()
166 << deliverMin.value_or(
STAmount{}).getFullText();
170 auto const& srcAsset = maxSourceAmount.
asset();
171 auto const& dstAsset = dstAmount.
asset();
173 bool const xrpDirect = srcAsset.
native() && dstAsset.native();
178 auto const dstAccountID = tx.getAccountID(sfDestination);
182 JLOG(j.trace()) <<
"Malformed transaction: "
183 <<
"Payment destination account not specified.";
188 JLOG(j.trace()) <<
"Malformed transaction: bad max amount: "
194 JLOG(j.trace()) <<
"Malformed transaction: bad dst amount: " << dstAmount.
getFullText();
197 auto bad = [&](
auto const& asset) {
202 if (bad(srcAsset) || bad(dstAsset))
204 JLOG(j.trace()) <<
"Malformed transaction: Bad currency.";
207 if (account == dstAccountID &&
equalTokens(srcAsset, dstAsset) && !hasPaths)
211 JLOG(j.trace()) <<
"Malformed transaction: "
212 <<
"Redundant payment from " <<
to_string(account)
213 <<
" to self without path for " <<
to_string(dstAsset);
216 if (xrpDirect && hasMax)
219 JLOG(j.trace()) <<
"Malformed transaction: "
220 <<
"SendMax specified for XRP to XRP.";
223 if ((xrpDirect || (!mpTokensV2 && isDstMPT)) && hasPaths)
226 JLOG(j.trace()) <<
"Malformed transaction: "
227 <<
"Paths specified for XRP to XRP or MPT to MPT.";
230 if (xrpDirect && partialPaymentAllowed)
233 JLOG(j.trace()) <<
"Malformed transaction: "
234 <<
"Partial payment specified for XRP to XRP.";
237 if ((xrpDirect || (!mpTokensV2 && isDstMPT)) && limitQuality)
240 JLOG(j.trace()) <<
"Malformed transaction: "
241 <<
"Limit quality specified for XRP to XRP or MPT to MPT.";
244 if ((xrpDirect || (!mpTokensV2 && isDstMPT)) && !defaultPathsAllowed)
247 JLOG(j.trace()) <<
"Malformed transaction: "
248 <<
"No ripple direct specified for XRP to XRP or MPT to MPT.";
254 if (!partialPaymentAllowed)
256 JLOG(j.trace()) <<
"Malformed transaction: Partial payment not "
258 << jss::DeliverMin.cStr() <<
".";
262 auto const dMin = *deliverMin;
265 JLOG(j.trace()) <<
"Malformed transaction: Invalid " << jss::DeliverMin.cStr()
266 <<
" amount. " << dMin.getFullText();
269 if (dMin.asset() != dstAmount.
asset())
271 JLOG(j.trace()) <<
"Malformed transaction: Dst issue differs "
273 << jss::DeliverMin.cStr() <<
". " << dMin.getFullText();
276 if (dMin > dstAmount)
278 JLOG(j.trace()) <<
"Malformed transaction: Dst amount less than "
279 << jss::DeliverMin.cStr() <<
". " << dMin.getFullText();
297 auto const& amountAsset = dstAmount.
asset();
300 if (tx.
isFieldPresent(sfSendMax) && tx[sfSendMax].asset() != amountAsset)
303 if (
isXRP(amountAsset))
306 return amountAsset.visit(
311 if (heldGranularPermissions.
contains(PaymentMint) &&
314 if (heldGranularPermissions.
contains(PaymentBurn) &&
315 mptIssue.
getIssuer() == tx[sfDestination])
325 auto const account = tx[sfAccount];
326 auto const destination = tx[sfDestination];
329 if (issue.getIssuer() != account && issue.getIssuer() != destination)
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 =
351 if (heldGranularPermissions.
contains(PaymentBurn) && accountIsHolder)
362 bool const partialPaymentAllowed = ctx.
tx.
isFlag(tfPartialPayment);
364 auto const sendMax = ctx.
tx[~sfSendMax];
366 AccountID const dstAccountID(ctx.
tx[sfDestination]);
370 auto const sleDst = ctx.
view.
read(k);
377 JLOG(ctx.
j.
trace()) <<
"Delay transaction: Destination account does not exist.";
384 if (partialPaymentAllowed)
390 JLOG(ctx.
j.
trace()) <<
"Delay transaction: Partial payment not "
391 "allowed to create account.";
403 if (!ctx.
tx.
isFlag(tfSponsorCreatedAccount))
408 JLOG(ctx.
j.
trace()) <<
"Delay transaction: Destination account does not exist. "
409 <<
"Insufficient payment to create account.";
418 else if (ctx.
tx.
isFlag(tfSponsorCreatedAccount))
425 else if (sleDst->isFlag(lsfRequireDestTag) && !ctx.
tx.
isFieldPresent(sfDestinationTag))
432 JLOG(ctx.
j.
trace()) <<
"Malformed transaction: DestinationTag required.";
438 if (hasPaths || sendMax || !dstAmount.
native())
443 return path.size() > kMaxPathLength;
474 auto const deliverMin =
ctx_.tx[~sfDeliverMin];
477 bool const partialPaymentAllowed =
ctx_.tx.isFlag(tfPartialPayment);
478 bool const limitQuality =
ctx_.tx.isFlag(tfLimitQuality);
479 bool const defaultPathsAllowed = !
ctx_.tx.isFlag(tfNoRippleDirect);
480 auto const hasPaths =
ctx_.tx.isFieldPresent(sfPaths);
481 auto const sendMax =
ctx_.tx[~sfSendMax];
483 AccountID const dstAccountID(
ctx_.tx.getAccountID(sfDestination));
484 STAmount const dstAmount(
ctx_.tx.getFieldAmount(sfAmount));
488 JLOG(
j_.trace()) <<
"maxSourceAmount=" << maxSourceAmount.getFullText()
499 sleDst->setAccountID(sfAccount, dstAccountID);
500 sleDst->setFieldU32(sfSequence,
view().seq());
503 if (
ctx_.tx.isFlag(tfSponsorCreatedAccount))
508 auto const currentSponsoringAccountCount =
509 sponsor->getFieldU32(sfSponsoringAccountCount);
513 JLOG(
j_.fatal()) <<
"Sponsoring account count overflow for account "
518 sponsor->setFieldU32(sfSponsoringAccountCount, currentSponsoringAccountCount + 1);
537 bool const ripple = (hasPaths || sendMax || !dstAmount.
native()) && (!isDstMPT || mpTokensV2);
563 JLOG(
j_.debug()) <<
"Entering RippleCalc in payment: " <<
ctx_.tx.getTransactionID();
570 ctx_.tx.getFieldPathSet(sfPaths),
571 ctx_.tx[~sfDomainID],
594 auto terResult = rc.
result();
606 JLOG(
j_.trace()) <<
" dstAmount=" << dstAmount.
getFullText();
624 auto const& issuer = mptIssue.
getIssuer();
627 Rate rate{QUALITY_ONE};
629 if (
accountID_ != issuer && dstAccountID != issuer)
651 if (partialPaymentAllowed && requiredMaxSourceAmount > maxSourceAmount)
653 requiredMaxSourceAmount = maxSourceAmount;
655 amountDeliver =
divide(maxSourceAmount, rate);
658 if (requiredMaxSourceAmount > maxSourceAmount ||
659 (deliverMin && amountDeliver < *deliverMin))
671 if (
view().rules().enabled(fixMPTDeliveredAmount) && amountDeliver != dstAmount)
672 ctx_.deliver(amountDeliver);
682 XRPL_ASSERT(dstAmount.
native(),
"xrpl::Payment::doApply : amount is XRP");
695 bool const accountIsPayer =
ctx_.tx.getFeePayerID() ==
accountID_;
700 auto const minRequiredFunds =
701 accountIsPayer ?
std::max(reserve,
ctx_.tx.getFieldAmount(sfFee).xrp()) : reserve;
708 <<
" / " <<
to_string(dstAmount.
xrp() + minRequiredFunds) <<
" ("
746 if (dstAmount > dstReserve || sleDst->getFieldAmount(sfBalance) > dstReserve)
755 sleSrc->setFieldAmount(sfBalance, sleSrc->getFieldAmount(sfBalance) - dstAmount);
756 sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(sfBalance) + dstAmount);
759 if (sleDst->isFlag(lsfPasswordSpent))
760 sleDst->clearFlag(lsfPasswordSpent);
A generic endpoint for log messages.
Stream trace() const
Severity stream access functions.
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)
constexpr bool native() const
A currency issued by an account.
AccountID const & getIssuer() const
A wrapper which makes credits unavailable to balances.
void apply(RawView &to)
Apply changes to base view.
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static bool checkExtraFeatures(PreflightContext const &ctx)
void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override
Inspect a single ledger entry modified by this transaction.
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.
static std::size_t const kMaxPathSize
static NotTEC checkGranularSemantics(ReadView const &view, STTx const &tx, std::unordered_set< GranularPermissionType > const &heldGranularPermissions)
static TER preclaim(PreclaimContext const &ctx)
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.
constexpr bool holds() const noexcept
constexpr TIss const & get() const
std::string getFullText() const override
std::uint64_t mantissa() const noexcept
bool native() const noexcept
Asset const & asset() const
int exponent() const noexcept
std::shared_ptr< STLedgerEntry > pointer
std::shared_ptr< STLedgerEntry const > const & const_ref
bool isFlag(std::uint32_t) const
bool isFieldPresent(SField const &field) const
STPathSet const & getFieldPathSet(SField const &field) const
STAmount const & getFieldAmount(SField const &field) const
std::vector< STPath >::size_type size() const
AccountID const accountID_
Class describing the consequences to the account of applying a transaction if the transaction consume...
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 ®istry, Input const *const pInputs=nullptr)
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)
Keylet account(AccountID const &id) noexcept
AccountID root.
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const ¤cy) noexcept
The index of a trust line for a given currency.
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.
STAmount divide(STAmount const &amount, Rate const &rate)
@ terNO_DELEGATE_PERMISSION
bool isTerRetry(TER x) noexcept
bool isXRP(AccountID const &c)
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.
STAmount getMaxSourceAmount(AccountID const &account, STAmount const &dstAmount, std::optional< STAmount > const &sendMax)
bool isLegalNet(STAmount const &value)
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
std::string to_string(BaseUInt< Bits, Tag > const &a)
void addSponsorToLedgerEntry(SLE::ref sle, SLE::const_ref sponsorSle, SF_ACCOUNT const &field=sfSponsor)
Stamp a reserve sponsor onto a ledger entry using an explicit sponsor SLE.
TERSubset< CanCvtToNotTEC > NotTEC
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
@ temBAD_SEND_XRP_PARTIAL
@ temBAD_SEND_XRP_NO_DIRECT
bool isTesSuccess(TER x) noexcept
bool isAnyFrozen(ReadView const &view, std::initializer_list< AccountID > const &accounts, MPTIssue const &mptIssue, std::uint8_t depth=0)
TERSubset< CanCvtToTER > TER
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.
@ tecNO_SPONSOR_PERMISSION
STAmount multiply(STAmount const &amount, Number const &frac, Number::RoundingMode rm)
BadAsset const & badAsset()
bool isPseudoAccount(SLE::const_pointer sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Returns true if and only if sleAcct is a pseudo-account or specific pseudo-accounts in pseudoFieldFil...
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
XRPAmount accountReserve(ReadView const &view, SLE::const_ref sle, beast::Journal j, Adjustment adj={})
Returns the account reserve, in drops.
constexpr bool equalTokens(Asset const &lhs, Asset const &rhs)
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, SLE::const_ref sleDst, beast::Journal j)
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.
std::optional< uint256 const > const parentBatchId
State information when preflighting a tx.
Represents a transfer rate.
void setResult(TER const value)