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/TokenHelpers.h>
14#include <xrpl/protocol/AccountID.h>
15#include <xrpl/protocol/Asset.h>
16#include <xrpl/protocol/Feature.h>
17#include <xrpl/protocol/Indexes.h>
18#include <xrpl/protocol/Issue.h>
19#include <xrpl/protocol/LedgerFormats.h>
20#include <xrpl/protocol/MPTIssue.h>
21#include <xrpl/protocol/Permissions.h>
22#include <xrpl/protocol/Quality.h>
23#include <xrpl/protocol/Rate.h>
24#include <xrpl/protocol/SField.h>
25#include <xrpl/protocol/STAmount.h>
26#include <xrpl/protocol/STLedgerEntry.h>
27#include <xrpl/protocol/STPathSet.h>
28#include <xrpl/protocol/STTx.h>
29#include <xrpl/protocol/TER.h>
30#include <xrpl/protocol/TxFlags.h>
31#include <xrpl/protocol/UintTypes.h>
32#include <xrpl/protocol/XRPAmount.h>
33#include <xrpl/protocol/jss.h>
34#include <xrpl/tx/Transactor.h>
35#include <xrpl/tx/applySteps.h>
36#include <xrpl/tx/paths/RippleCalc.h>
50 STAmount const maxAmount = tx.isFieldPresent(sfSendMax) ? tx[sfSendMax] : tx[sfAmount];
54 return maxAmount.
native() ? maxAmount.
xrp() : beast::kZero;
71 [&](
MPTIssue const& issue) {
return dstAmount; },
72 [&](
Issue const& issue) {
76 Issue{issue.currency, account},
79 dstAmount < beast::kZero);
99 STAmount const dstAmount(tx.getFieldAmount(sfAmount));
101 bool const mpTokensV2 = ctx.
rules.
enabled(featureMPTokensV2);
105 (isDstMPT && !mpTokensV2) ? kTfMptPaymentMaskV1 : tfPaymentMask;
116 STAmount const dstAmount(tx.getFieldAmount(sfAmount));
118 bool const mpTokensV2 = ctx.
rules.
enabled(featureMPTokensV2);
128 if (
auto const domainID = tx[~sfDomainID];
129 ctx.
rules.
enabled(fixCleanup3_2_0) && domainID && *domainID == beast::kZero)
132 bool const partialPaymentAllowed = tx.isFlag(tfPartialPayment);
133 bool const limitQuality = tx.isFlag(tfLimitQuality);
134 bool const defaultPathsAllowed = !tx.isFlag(tfNoRippleDirect);
135 bool const hasPaths = tx.isFieldPresent(sfPaths);
136 bool const hasMax = tx.isFieldPresent(sfSendMax);
138 auto const deliverMin = tx[~sfDeliverMin];
140 auto const account = tx.getAccountID(sfAccount);
144 ((isDstMPT && dstAmount.
asset() != maxSourceAmount.
asset()) ||
147 JLOG(j.trace()) <<
"Malformed transaction: inconsistent issues: " << dstAmount.
getFullText()
149 << deliverMin.value_or(
STAmount{}).getFullText();
153 auto const& srcAsset = maxSourceAmount.
asset();
154 auto const& dstAsset = dstAmount.
asset();
156 bool const xrpDirect = srcAsset.
native() && dstAsset.native();
161 auto const dstAccountID = tx.getAccountID(sfDestination);
165 JLOG(j.trace()) <<
"Malformed transaction: "
166 <<
"Payment destination account not specified.";
169 if (hasMax && maxSourceAmount <= beast::kZero)
171 JLOG(j.trace()) <<
"Malformed transaction: bad max amount: "
175 if (dstAmount <= beast::kZero)
177 JLOG(j.trace()) <<
"Malformed transaction: bad dst amount: " << dstAmount.
getFullText();
180 auto bad = [&](
auto const& asset) {
185 if (bad(srcAsset) || bad(dstAsset))
187 JLOG(j.trace()) <<
"Malformed transaction: Bad currency.";
190 if (account == dstAccountID &&
equalTokens(srcAsset, dstAsset) && !hasPaths)
194 JLOG(j.trace()) <<
"Malformed transaction: "
195 <<
"Redundant payment from " <<
to_string(account)
196 <<
" to self without path for " <<
to_string(dstAsset);
199 if (xrpDirect && hasMax)
202 JLOG(j.trace()) <<
"Malformed transaction: "
203 <<
"SendMax specified for XRP to XRP.";
206 if ((xrpDirect || (!mpTokensV2 && isDstMPT)) && hasPaths)
209 JLOG(j.trace()) <<
"Malformed transaction: "
210 <<
"Paths specified for XRP to XRP or MPT to MPT.";
213 if (xrpDirect && partialPaymentAllowed)
216 JLOG(j.trace()) <<
"Malformed transaction: "
217 <<
"Partial payment specified for XRP to XRP.";
220 if ((xrpDirect || (!mpTokensV2 && isDstMPT)) && limitQuality)
223 JLOG(j.trace()) <<
"Malformed transaction: "
224 <<
"Limit quality specified for XRP to XRP or MPT to MPT.";
227 if ((xrpDirect || (!mpTokensV2 && isDstMPT)) && !defaultPathsAllowed)
230 JLOG(j.trace()) <<
"Malformed transaction: "
231 <<
"No ripple direct specified for XRP to XRP or MPT to MPT.";
237 if (!partialPaymentAllowed)
239 JLOG(j.trace()) <<
"Malformed transaction: Partial payment not "
241 << jss::DeliverMin.cStr() <<
".";
245 auto const dMin = *deliverMin;
246 if (!
isLegalNet(dMin) || dMin <= beast::kZero)
248 JLOG(j.trace()) <<
"Malformed transaction: Invalid " << jss::DeliverMin.cStr()
249 <<
" amount. " << dMin.getFullText();
252 if (dMin.asset() != dstAmount.
asset())
254 JLOG(j.trace()) <<
"Malformed transaction: Dst issue differs "
256 << jss::DeliverMin.cStr() <<
". " << dMin.getFullText();
259 if (dMin > dstAmount)
261 JLOG(j.trace()) <<
"Malformed transaction: Dst amount less than "
262 << jss::DeliverMin.cStr() <<
". " << dMin.getFullText();
280 auto const& amountAsset = dstAmount.
asset();
283 if (tx.
isFieldPresent(sfSendMax) && tx[sfSendMax].asset() != amountAsset)
286 if (
isXRP(amountAsset))
289 return amountAsset.visit(
294 if (heldGranularPermissions.
contains(PaymentMint) &&
297 if (heldGranularPermissions.
contains(PaymentBurn) &&
298 mptIssue.
getIssuer() == tx[sfDestination])
308 auto const account = tx[sfAccount];
309 auto const destination = tx[sfDestination];
312 if (issue.getIssuer() != account && issue.getIssuer() != destination)
319 bool const accountIsLow = (account < destination);
320 auto const destLimit = sle->getFieldAmount(accountIsLow ? sfHighLimit : sfLowLimit);
321 auto const rawBalance = sle->getFieldAmount(sfBalance);
322 bool const accountIsHolder =
323 accountIsLow ? rawBalance > beast::kZero : rawBalance < beast::kZero;
328 if (heldGranularPermissions.
contains(PaymentMint) && destLimit > beast::kZero &&
334 if (heldGranularPermissions.
contains(PaymentBurn) && accountIsHolder)
345 bool const partialPaymentAllowed = ctx.
tx.
isFlag(tfPartialPayment);
347 auto const sendMax = ctx.
tx[~sfSendMax];
349 AccountID const dstAccountID(ctx.
tx[sfDestination]);
353 auto const sleDst = ctx.
view.
read(k);
360 JLOG(ctx.
j.
trace()) <<
"Delay transaction: Destination account does not exist.";
366 if (ctx.
view.
open() && partialPaymentAllowed)
370 JLOG(ctx.
j.
trace()) <<
"Delay transaction: Partial payment not "
371 "allowed to create account.";
381 JLOG(ctx.
j.
trace()) <<
"Delay transaction: Destination account does not exist. "
382 <<
"Insufficent payment to create account.";
390 else if (sleDst->isFlag(lsfRequireDestTag) && !ctx.
tx.
isFieldPresent(sfDestinationTag))
397 JLOG(ctx.
j.
trace()) <<
"Malformed transaction: DestinationTag required.";
403 if ((hasPaths || sendMax || !dstAmount.
native()) && ctx.
view.
open())
408 return path.size() > kMaxPathLength;
434 auto const deliverMin =
ctx_.tx[~sfDeliverMin];
437 bool const partialPaymentAllowed =
ctx_.tx.isFlag(tfPartialPayment);
438 bool const limitQuality =
ctx_.tx.isFlag(tfLimitQuality);
439 bool const defaultPathsAllowed = !
ctx_.tx.isFlag(tfNoRippleDirect);
440 auto const hasPaths =
ctx_.tx.isFieldPresent(sfPaths);
441 auto const sendMax =
ctx_.tx[~sfSendMax];
443 AccountID const dstAccountID(
ctx_.tx.getAccountID(sfDestination));
444 STAmount const dstAmount(
ctx_.tx.getFieldAmount(sfAmount));
448 JLOG(
j_.trace()) <<
"maxSourceAmount=" << maxSourceAmount.getFullText()
459 sleDst->setAccountID(sfAccount, dstAccountID);
460 sleDst->setFieldU32(sfSequence,
view().seq());
461 sleDst->setFieldAmount(sfBalance,
XRPAmount(beast::kZero));
476 bool const ripple = (hasPaths || sendMax || !dstAmount.
native()) && (!isDstMPT || mpTokensV2);
502 JLOG(
j_.debug()) <<
"Entering RippleCalc in payment: " <<
ctx_.tx.getTransactionID();
509 ctx_.tx.getFieldPathSet(sfPaths),
510 ctx_.tx[~sfDomainID],
533 auto terResult = rc.
result();
545 JLOG(
j_.trace()) <<
" dstAmount=" << dstAmount.
getFullText();
563 auto const& issuer = mptIssue.
getIssuer();
566 Rate rate{QUALITY_ONE};
568 if (
accountID_ != issuer && dstAccountID != issuer)
590 if (partialPaymentAllowed && requiredMaxSourceAmount > maxSourceAmount)
592 requiredMaxSourceAmount = maxSourceAmount;
594 amountDeliver =
divide(maxSourceAmount, rate);
597 if (requiredMaxSourceAmount > maxSourceAmount ||
598 (deliverMin && amountDeliver < *deliverMin))
610 if (
view().rules().enabled(fixMPTDeliveredAmount) && amountDeliver != dstAmount)
611 ctx_.deliver(amountDeliver);
621 XRPL_ASSERT(dstAmount.
native(),
"xrpl::Payment::doApply : amount is XRP");
631 auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
638 bool const accountIsPayer = (
ctx_.tx.getFeePayer() ==
accountID_);
643 auto const minRequiredFunds =
644 accountIsPayer ?
std::max(reserve,
ctx_.tx.getFieldAmount(sfFee).xrp()) : reserve;
651 <<
" / " <<
to_string(dstAmount.
xrp() + minRequiredFunds) <<
" ("
689 if (dstAmount > dstReserve || sleDst->getFieldAmount(sfBalance) > dstReserve)
698 sleSrc->setFieldAmount(sfBalance, sleSrc->getFieldAmount(sfBalance) - dstAmount);
699 sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(sfBalance) + dstAmount);
702 if (sleDst->isFlag(lsfPasswordSpent))
703 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, 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)
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)
TERSubset< CanCvtToNotTEC > NotTEC
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No, AllowMPTOverflow allowOverflow=AllowMPTOverflow::No)
Calls static accountSendIOU if saAmount represents Issue.
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.
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...
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 accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
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.
State information when preflighting a tx.
Represents a transfer rate.
void setResult(TER const value)