2222#include < xrpl/basics/chrono.h>
2323#include < xrpl/beast/utility/instrumentation.h>
2424#include < xrpl/ledger/CredentialHelpers.h>
25+ #include < xrpl/ledger/Credit.h>
2526#include < xrpl/ledger/ReadView.h>
2627#include < xrpl/ledger/View.h>
2728#include < xrpl/protocol/Feature.h>
@@ -1361,12 +1362,58 @@ checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
13611362 return tesSUCCESS;
13621363}
13631364
1365+ /*
1366+ * Checks if a withdrawal amount into the destination account exceeds
1367+ * any applicable receiving limit.
1368+ * Called by VaultWithdraw and LoanBrokerCoverWithdraw.
1369+ *
1370+ * IOU : Performs the trustline check against the destination account's
1371+ * credit limit to ensure the account's trust maximum is not exceeded.
1372+ *
1373+ * MPT: The limit check is effectively skipped (returns true). This is
1374+ * because MPT MaximumAmount relates to token supply, and withdrawal does not
1375+ * involve minting new tokens that could exceed the global cap.
1376+ * On withdrawal, tokens are simply transferred from the vault's pseudo-account
1377+ * to the destination account. Since no new MPT tokens are minted during this
1378+ * transfer, the withdrawal cannot violate the MPT MaximumAmount/supply cap
1379+ * even if `from` is the issuer.
1380+ */
1381+ static TER
1382+ withdrawToDestExceedsLimit (
1383+ ReadView const & view,
1384+ AccountID const & from,
1385+ AccountID const & to,
1386+ STAmount const & amount)
1387+ {
1388+ auto const & issuer = amount.getIssuer ();
1389+ if (from == to || to == issuer || isXRP (issuer))
1390+ return tesSUCCESS;
1391+
1392+ return std::visit (
1393+ [&]<ValidIssueType TIss>(TIss const & issue) -> TER {
1394+ if constexpr (std::is_same_v<TIss, Issue>)
1395+ {
1396+ auto const & currency = issue.currency ;
1397+ auto const owed = creditBalance (view, to, issuer, currency);
1398+ if (owed <= beast::zero)
1399+ {
1400+ auto const limit = creditLimit (view, to, issuer, currency);
1401+ if (-owed >= limit || amount > (limit + owed))
1402+ return tecNO_LINE;
1403+ }
1404+ }
1405+ return tesSUCCESS;
1406+ },
1407+ amount.asset ().value ());
1408+ }
1409+
13641410[[nodiscard]] TER
13651411canWithdraw (
13661412 AccountID const & from,
13671413 ReadView const & view,
13681414 AccountID const & to,
13691415 SLE::const_ref toSle,
1416+ STAmount const & amount,
13701417 bool hasDestinationTag)
13711418{
13721419 if (auto const ret = checkDestinationAndTag (toSle, hasDestinationTag))
@@ -1381,19 +1428,20 @@ canWithdraw(
13811428 return tecNO_PERMISSION;
13821429 }
13831430
1384- return tesSUCCESS ;
1431+ return withdrawToDestExceedsLimit (view, from, to, amount) ;
13851432}
13861433
13871434[[nodiscard]] TER
13881435canWithdraw (
13891436 AccountID const & from,
13901437 ReadView const & view,
13911438 AccountID const & to,
1439+ STAmount const & amount,
13921440 bool hasDestinationTag)
13931441{
13941442 auto const toSle = view.read (keylet::account (to));
13951443
1396- return canWithdraw (from, view, to, toSle, hasDestinationTag);
1444+ return canWithdraw (from, view, to, toSle, amount, hasDestinationTag);
13971445}
13981446
13991447[[nodiscard]] TER
@@ -1402,7 +1450,8 @@ canWithdraw(ReadView const& view, STTx const& tx)
14021450 auto const from = tx[sfAccount];
14031451 auto const to = tx[~sfDestination].value_or (from);
14041452
1405- return canWithdraw (from, view, to, tx.isFieldPresent (sfDestinationTag));
1453+ return canWithdraw (
1454+ from, view, to, tx[sfAmount], tx.isFieldPresent (sfDestinationTag));
14061455}
14071456
14081457TER
0 commit comments