xrpld
Loading...
Searching...
No Matches
VaultWithdraw.cpp
1#include <xrpl/tx/transactors/vault/VaultWithdraw.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Number.h>
5#include <xrpl/basics/base_uint.h>
6#include <xrpl/beast/utility/Zero.h>
7#include <xrpl/beast/utility/instrumentation.h>
8#include <xrpl/ledger/ReadView.h>
9#include <xrpl/ledger/View.h>
10#include <xrpl/ledger/helpers/AccountRootHelpers.h>
11#include <xrpl/ledger/helpers/CredentialHelpers.h>
12#include <xrpl/ledger/helpers/TokenHelpers.h>
13#include <xrpl/ledger/helpers/VaultHelpers.h>
14#include <xrpl/protocol/AccountID.h>
15#include <xrpl/protocol/Feature.h>
16#include <xrpl/protocol/Indexes.h>
17#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
18#include <xrpl/protocol/MPTIssue.h>
19#include <xrpl/protocol/Protocol.h>
20#include <xrpl/protocol/SField.h>
21#include <xrpl/protocol/STLedgerEntry.h>
22#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
23#include <xrpl/protocol/STTakesAsset.h>
24#include <xrpl/protocol/STTx.h>
25#include <xrpl/protocol/TER.h>
26#include <xrpl/protocol/XRPAmount.h>
27#include <xrpl/tx/Transactor.h>
28
29#include <stdexcept>
30
31namespace xrpl {
32
33bool
35{
36 return !ctx.tx.isFieldPresent(sfCredentialIDs) ||
37 (ctx.rules.enabled(featureCredentials) && ctx.rules.enabled(fixCleanup3_4_0));
38}
39
41shouldWaiveWithdrawal(ReadView const& view, AccountID const& account, SLE::ConstRef issuance)
42{
43 XRPL_ASSERT(
44 issuance && issuance->getType() == ltMPTOKEN_ISSUANCE,
45 "xrpl::shouldWaiveWithdrawal : valid issuance sle");
46
47 return view.rules().enabled(fixCleanup3_2_0) && isSoleShareholder(view, account, issuance)
50}
51
54{
55 if (ctx.tx[sfVaultID] == beast::kZero)
56 {
57 JLOG(ctx.j.debug()) << "VaultWithdraw: zero/empty vault ID.";
58 return temMALFORMED;
59 }
60
61 if (ctx.tx[sfAmount] <= beast::kZero)
62 return temBAD_AMOUNT;
63
64 if (auto const destination = ctx.tx[~sfDestination])
65 {
66 if (*destination == beast::kZero)
67 {
68 return temMALFORMED;
69 }
70 }
71
72 if (auto const err = credentials::checkFields(ctx.tx, ctx.rules, ctx.j); !isTesSuccess(err))
73 return err;
74
75 return tesSUCCESS;
76}
77
78TER
80{
81 auto const fix313Enabled = ctx.view.rules().enabled(fixCleanup3_1_3);
82 auto const fix320Enabled = ctx.view.rules().enabled(fixCleanup3_2_0);
83 auto const fix330Enabled = ctx.view.rules().enabled(fixCleanup3_3_0);
84 auto const fix340Enabled = ctx.view.rules().enabled(fixCleanup3_4_0);
85
86 auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
87 if (!vault)
88 return tecNO_ENTRY;
89
90 if (ctx.view.rules().enabled(featureLendingProtocolV1_1))
91 {
92 if (getVaultPhase(ctx.view, vault) == VaultPhase::Investment)
93 {
94 JLOG(ctx.j.debug())
95 << "VaultWithdraw: vault withdrawal is not allowed in the investment phase.";
96 return tecTOO_SOON;
97 }
98 }
99
100 auto const amount = ctx.tx[sfAmount];
101 auto const vaultAsset = vault->at(sfAsset);
102 auto const vaultShare = vault->at(sfShareMPTID);
103 if (amount.asset() != vaultAsset && amount.asset() != vaultShare)
104 return tecWRONG_ASSET;
105
106 auto const& vaultAccount = vault->at(sfAccount);
107 auto const& account = ctx.tx[sfAccount];
108 auto const& dstAcct = ctx.tx[~sfDestination].value_or(account);
109 // Post-fixCleanup3_2_0: withdraw is a recovery path that bypasses the
110 // lsfMPTCanTransfer flag check, so an issuer cannot trap depositor funds.
111 // Other transferability checks (IOU NoRipple, freeze, requireAuth) still
112 // apply.
113 auto const waive = fix320Enabled ? WaiveMPTCanTransfer::Yes : WaiveMPTCanTransfer::No;
114 if (auto ter = canTransfer(ctx.view, vaultAsset, vaultAccount, dstAcct, waive);
115 !isTesSuccess(ter))
116 {
117 JLOG(ctx.j.debug()) << "VaultWithdraw: vault assets are non-transferable.";
118 return ter;
119 }
120
121 // Enforce valid withdrawal policy
122 if (vault->at(sfWithdrawalPolicy) != kVaultStrategyFirstComeFirstServe)
123 {
124 // LCOV_EXCL_START
125 JLOG(ctx.j.error()) << "VaultWithdraw: invalid withdrawal policy.";
126 return tefINTERNAL;
127 // LCOV_EXCL_STOP
128 }
129
130 // Validate credentials (if any) before canWithdraw, since canWithdraw may
131 // call credentials::authorizedDepositPreauth which assumes credentials
132 // already exist.
133 if (auto const err = credentials::valid(ctx.tx, ctx.view, account, ctx.j); !isTesSuccess(err))
134 return err;
135
136 // A pseudo-account belongs to a ledger object rather than to a person and
137 // must never receive funds from a user-initiated transaction. Deposit
138 // authorization, which every pseudo-account carries, already refuses the
139 // payout, but it reports only that the destination declines deposits and
140 // leaves the real reason unsaid.
141 if (fix340Enabled && isPseudoAccount(ctx.view, dstAcct))
142 {
143 JLOG(ctx.j.debug()) << "VaultWithdraw: cannot withdraw into a pseudo-account.";
144 return tecPSEUDO_ACCOUNT;
145 }
146
147 if (fix313Enabled && amount.asset() == vaultShare)
148 {
149 // Post-fixCleanup3_1_3: if the user specified shares, convert
150 // to the equivalent asset amount before checking withdrawal
151 // limits. Pre-amendment the limit check was skipped for
152 // share-denominated withdrawals.
153 auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(vaultShare));
154 if (!sleIssuance)
155 {
156 // LCOV_EXCL_START
157 JLOG(ctx.j.error()) << "VaultWithdraw: missing issuance of vault shares.";
158 return tefINTERNAL;
159 // LCOV_EXCL_STOP
160 }
161
162 // When the user is the sole shareholder they own both the available and future value.
163 // We waive the unrealized-loss subtraction in this case to avoid user withdrawing all of
164 // their shares but keeping future value in the vault.
165 auto const waiveUnrealizedLoss = shouldWaiveWithdrawal(ctx.view, account, sleIssuance);
166 try
167 {
168 auto const maybeAssets =
169 sharesToAssetsWithdraw(vault, sleIssuance, amount, waiveUnrealizedLoss);
170 if (!maybeAssets)
171 return tefINTERNAL; // LCOV_EXCL_LINE
172
173 if (auto const ret = canWithdraw(
174 ctx.view,
175 account,
176 dstAcct,
177 *maybeAssets,
178 ctx.tx.isFieldPresent(sfDestinationTag),
179 ctx.tx[~sfCredentialIDs]))
180 return ret;
181 }
182 catch (std::overflow_error const&)
183 {
184 // It's easy to hit this exception from Number with large enough Scale
185 // so we avoid spamming the log and only use debug here.
186 JLOG(ctx.j.debug()) //
187 << "VaultWithdraw: overflow error with"
188 << " scale=" << (int)vault->at(sfScale) //
189 << ", assetsTotal=" << vault->at(sfAssetsTotal)
190 << ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount)
191 << ", amount=" << amount.value();
192 return tecPATH_DRY;
193 }
194 }
195 else
196 {
197 if (auto const ret = canWithdraw(ctx.view, ctx.tx))
198 return ret;
199 }
200
201 // If sending to Account (i.e. not a transfer), we will also create (only
202 // if authorized) a trust line or MPToken as needed, in doApply().
203 // Destination MPToken or trust line must exist if _not_ sending to Account.
204 AuthType const authType = account == dstAcct ? AuthType::WeakAuth : AuthType::StrongAuth;
205 if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct, authType); !isTesSuccess(ter))
206 return ter;
207
208 // Fail early when self-destination would have to create a holding.
209 // Skip when a holding already exists: canAddHolding does not look at that,
210 // and would block a no-op create (the DefaultRipple-cleared self-withdraw).
211 if (fix340Enabled && account == dstAcct && !holdingExists(ctx.view, dstAcct, vaultAsset))
212 {
213 if (auto const ter = canAddHolding(ctx.view, vaultAsset); !isTesSuccess(ter))
214 return ter;
215 }
216
217 // The checks above only establish that an account may hold the asset. A
218 // private vault additionally restricts who may take part in it, so paying
219 // its asset out to a third party requires both ends of that payout to be
220 // inside the vault's permissioned domain. VaultDeposit applies the same
221 // domain check on the way in.
222 //
223 // Two cases deliberately skip the check. Withdrawing to self is never
224 // restricted: losing vault access must not strand funds already deposited.
225 // The asset issuer is always allowed to receive, which keeps the return
226 // path for frozen assets open even for a submitter who lost access.
227 if (fix340Enabled && vault->isFlag(lsfVaultPrivate) && dstAcct != account &&
228 dstAcct != vaultAsset.getIssuer())
229 {
230 auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(vaultShare));
231 if (!sleIssuance)
232 {
233 // LCOV_EXCL_START
234 JLOG(ctx.j.error()) << "VaultWithdraw: missing issuance of vault shares.";
235 return tefINTERNAL;
236 // LCOV_EXCL_STOP
237 }
238
239 // Unlike VaultDeposit we do not suppress tecEXPIRED: there is no
240 // doApply step here that would clean up the expired credential.
241 if (auto const ter = checkVaultDomain(ctx.view, sleIssuance, account, SuppressExpired::No);
242 !isTesSuccess(ter))
243 return ter;
244
245 if (auto const ter = checkVaultDomain(ctx.view, sleIssuance, dstAcct, SuppressExpired::No);
246 !isTesSuccess(ter))
247 return ter;
248 }
249
250 if (fix330Enabled)
251 {
252 // checkWithdrawFreeze checks the underlying asset on the source
253 // (vault pseudo-account), the submitter, and the destination.
254 // A separate share-level freeze check is unnecessary: vault shares
255 // are issued by the vault pseudo-account, which cannot submit
256 // MPTokenIssuanceSet to individually lock a holder's MPToken.
257 // The only way shares become locked is transitively via the
258 // underlying asset, which checkWithdrawFreeze covers.
259 if (auto const ret =
260 checkWithdrawFreeze(ctx.view, vaultAccount, account, dstAcct, vaultAsset))
261 return ret;
262 }
263 else
264 {
265 // Cannot withdraw from a Vault an Asset frozen for the destination account
266 if (auto const ret = checkFrozen(ctx.view, dstAcct, vaultAsset))
267 return ret;
268
269 // Cannot return shares to the vault, if the underlying asset was frozen for
270 // the submitter
271 if (auto const ret = checkFrozen(ctx.view, account, Asset{vaultShare}))
272 return ret;
273 }
274 return tesSUCCESS;
275}
276
277TER
279{
280 bool const fix340Enabled = view().rules().enabled(fixCleanup3_4_0);
281 auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
282 auto applyViewContext = ctx_.getApplyViewContext();
283 if (!vault)
284 return tefINTERNAL; // LCOV_EXCL_LINE
285
286 auto const mptIssuanceID = *((*vault)[sfShareMPTID]);
287 auto const sleIssuance = view().read(keylet::mptokenIssuance(mptIssuanceID));
288 if (!sleIssuance)
289 {
290 // LCOV_EXCL_START
291 JLOG(j_.error()) << "VaultWithdraw: missing issuance of vault shares.";
292 return tefINTERNAL;
293 // LCOV_EXCL_STOP
294 }
295
296 // Note, we intentionally do not check lsfVaultPrivate flag on the Vault. If
297 // you have a share in the vault, it means you were at some point authorized
298 // to deposit into it, and this means you are also indefinitely authorized
299 // to withdraw it to yourself. Sending the proceeds to somebody else is a
300 // different matter, and preclaim checks such a withdrawal against the
301 // vault's permissioned domain.
302
303 auto const amount = ctx_.tx[sfAmount];
304 Asset const vaultAsset = vault->at(sfAsset);
305
306 MPTIssue const share{mptIssuanceID};
307 STAmount sharesRedeemed = {share};
308 STAmount assetsWithdrawn;
309
310 // When the user is the sole shareholder they own both the available and future value.
311 // We waive the unrealized-loss subtraction in this case to avoid user withdrawing all of their
312 // shares but keeping future value in the vault.
313 auto const waiveUnrealizedLoss = shouldWaiveWithdrawal(view(), accountID_, sleIssuance);
314 // Number arithmetic can throw overflow_error when Scale and totals are large. Caught below.
315 try
316 {
317 if (amount.asset() == vaultAsset)
318 {
319 // Fixed assets, variable shares.
320 //
321 // Pre-fixCleanup3_4_0: shares were rounded to nearest, so the
322 // round-trip back to assets could exceed the requested amount.
323 // That over-delivers to the depositor and can bypass the
324 // preclaim canWithdraw check on the destination, which was
325 // validated against the requested amount only.
326 // Post-amendment: truncate shares so assetsWithdrawn <=
327 // requested amount by construction. If truncation yields zero
328 // shares, the tecPRECISION_LOSS guard below fires.
329 auto const truncate =
331 {
332 auto const maybeShares = assetsToSharesWithdraw(
333 vault, sleIssuance, amount, truncate, waiveUnrealizedLoss);
334 if (!maybeShares)
335 return tecINTERNAL; // LCOV_EXCL_LINE
336 sharesRedeemed = *maybeShares;
337 }
338
339 // Shares are MPT (integer). Small requested amounts truncate to zero; refuse rather
340 // than burn nothing while paying out assets.
341 if (sharesRedeemed == beast::kZero)
342 return tecPRECISION_LOSS;
343 // Convert shares back to assets so the payout matches the shares actually burned, not
344 // the requested amount. The extra would otherwise be paid from the vault for free.
345 auto const maybeAssets =
346 sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed, waiveUnrealizedLoss);
347 if (!maybeAssets)
348 return tecINTERNAL; // LCOV_EXCL_LINE
349 assetsWithdrawn = *maybeAssets;
350 }
351 else if (amount.asset() == share)
352 {
353 // Fixed shares, variable assets. No round-trip: the share count is exactly what the
354 // caller specified; only the payout amount is derived.
355 sharesRedeemed = amount;
356 auto const maybeAssets =
357 sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed, waiveUnrealizedLoss);
358 if (!maybeAssets)
359 return tecINTERNAL; // LCOV_EXCL_LINE
360 assetsWithdrawn = *maybeAssets;
361 }
362 else
363 {
364 return tefINTERNAL; // LCOV_EXCL_LINE
365 }
366 }
367 catch (std::overflow_error const&)
368 {
369 // It's easy to hit this exception from Number with large enough Scale
370 // so we avoid spamming the log and only use debug here.
371 JLOG(j_.debug()) //
372 << "VaultWithdraw: overflow error with"
373 << " scale=" << (int)vault->at(sfScale).value() //
374 << ", assetsTotal=" << vault->at(sfAssetsTotal).value()
375 << ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount)
376 << ", amount=" << amount.value();
377 // Overflow means this transaction cannot apply, but ledger state is still consistent.
378 // Return tecPATH_DRY rather than a hard internal error.
379 return tecPATH_DRY;
380 }
381
382 // The "final withdrawal" rule below handles its own zero-value case using
383 // sfAssetsAvailable directly, so it is exempt from the checks below.
384 bool const isFinalWithdrawal =
385 sharesRedeemed == STAmount{share, sleIssuance->at(sfOutstandingAmount)};
386
387 auto assetsAvailable = vault->at(sfAssetsAvailable);
388 auto assetsTotal = vault->at(sfAssetsTotal);
389 auto const lossUnrealized = vault->at(sfLossUnrealized);
390
391 if (fix340Enabled && !isFinalWithdrawal)
392 {
393 // Fixed-shares path: a small share count can round to zero assets even though the vault has
394 // backing value. Reject rather than burn shares for a zero payout. The fixed-assets branch
395 // above has already rejected zero via the sharesRedeemed check.
396 if (amount.asset() == share && assetsWithdrawn == beast::kZero &&
397 assetsTotalForWithdrawal(vault, waiveUnrealizedLoss) != beast::kZero)
398 {
399 JLOG(j_.debug()) << "VaultWithdraw: fixed-share withdrawal rounds to zero assets";
400 return tecPRECISION_LOSS;
401 }
402
403 // Number arithmetic can throw overflow_error when Scale and totals are large.
404 try
405 {
406 // A non-zero payout can be too small to change the stored sfAssetsTotal at
407 // STAmount's precision. Shares would still be burned, reject it instead.
408 if (debitIsNonZeroDust(vaultAsset, assetsTotal, assetsWithdrawn))
409 {
410 JLOG(j_.debug()) << "VaultWithdraw: withdrawal amount too small to change stored"
411 " vault balance";
412 return tecPRECISION_LOSS;
413 }
414 }
415 // LCOV_EXCL_START
416 catch (std::overflow_error const&)
417 {
418 // It's easy to hit this exception from Number with large enough Scale
419 // so we avoid spamming the log and only use debug here.
420 JLOG(j_.debug()) //
421 << "VaultWithdraw: overflow error with"
422 << " scale=" << (int)vault->at(sfScale).value() //
423 << ", assetsTotal=" << vault->at(sfAssetsTotal).value()
424 << ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount)
425 << ", amount=" << amount.value();
426 // Overflow means this transaction cannot apply, but ledger state is still consistent.
427 // Return tecPATH_DRY rather than a hard internal error.
428 return tecPATH_DRY;
429 }
430 // LCOV_EXCL_STOP
431 }
432
433 // Post-fixCleanup3_3_0: preclaim already validated all freeze conditions
434 // (checkWithdrawFreeze), so IgnoreFreeze avoids a redundant check that
435 // would incorrectly return zero for vault pseudo-accounts whose shares
436 // are frozen via a transitively frozen underlying asset.
437 auto const freezeHandling = view().rules().enabled(fixCleanup3_3_0)
440 if (accountHolds(view(), accountID_, share, freezeHandling, AuthHandling::IgnoreAuth, j_) <
441 sharesRedeemed)
442 {
443 JLOG(j_.debug()) << "VaultWithdraw: account doesn't hold enough shares";
445 }
446
447 // Post-fixCleanup3_4_0: round the payout to the sfAssetsTotal scale so all three rails
448 // (trust line / MPT, sfAssetsAvailable, sfAssetsTotal) change by the same representable delta.
449 // Skip when assetsWithdrawn is already zero: the earlier fix340 guard above deliberately
450 // permits fixed-share zero-asset withdrawals in a fully-impaired vault (where
451 // assetsTotalForWithdrawal == 0), and clamping-then-rejecting would undo that. Also skip on
452 // the final-withdrawal path, which overwrites assetsWithdrawn with sfAssetsAvailable below.
453 if (fix340Enabled && !isFinalWithdrawal && assetsWithdrawn > beast::kZero)
454 {
455 // Check availability against the unclamped amount first, so a withdrawal that is both
456 // over the vault's available balance and sub-ULP at the posterior sfAssetsTotal scale
457 // reports tecINSUFFICIENT_FUNDS rather than tecPRECISION_LOSS. The clamp below only ever
458 // shrinks assetsWithdrawn, so this check stays valid; the post-clamp check further down
459 // remains in place to catch the (now smaller) clamped value too.
460 if (*assetsAvailable < assetsWithdrawn)
461 {
462 JLOG(j_.debug()) << "VaultWithdraw: vault doesn't hold enough assets";
464 }
465
466 // Number arithmetic can throw overflow_error when Scale and totals are large.
467 try
468 {
469 // Round down at the posterior sfAssetsTotal scale so the payout never exceeds the
470 // value represented by the redeemed shares. sharesRedeemed is intentionally not
471 // re-derived: any trimmed residue stays with remaining shareholders.
472 auto const maybeClamped = clampToAssetsTotalScale(vault, -assetsWithdrawn);
473 if (!maybeClamped)
474 return maybeClamped.error(); // LCOV_EXCL_LINE
475 assetsWithdrawn = *maybeClamped;
476 }
477 // LCOV_EXCL_START
478 catch (std::overflow_error const&)
479 {
480 // It's easy to hit this exception from Number with large enough Scale
481 // so we avoid spamming the log and only use debug here.
482 JLOG(j_.debug()) //
483 << "VaultWithdraw: overflow error with"
484 << " scale=" << (int)vault->at(sfScale).value() //
485 << ", assetsTotal=" << vault->at(sfAssetsTotal).value()
486 << ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount)
487 << ", amount=" << amount.value();
488 // Overflow means this transaction cannot apply, but ledger state is still consistent.
489 // Return tecPATH_DRY rather than a hard internal error.
490 return tecPATH_DRY;
491 }
492 // LCOV_EXCL_STOP
493 }
494
495 // The vault must have enough assets on hand.
496 if (*assetsAvailable < assetsWithdrawn)
497 {
498 JLOG(j_.debug()) << "VaultWithdraw: vault doesn't hold enough assets";
500 }
501
502 // Post-fixCleanup3_2_0: burning every outstanding share is only allowed when the vault has no
503 // unrealized loss. Otherwise the resulting (shares == 0, assetsTotal > 0) state would violate
504 // the zero-sized-vault invariant.
505 //
506 // The payout is set to the remaining sfAssetsAvailable. The helper result should already
507 // equal that value in a clean vault; any mismatch is a rounding artifact and is logged.
508 if (view().rules().enabled(fixCleanup3_2_0) && isFinalWithdrawal)
509 {
510 // Unreachable: a final withdrawal with lossUnrealized > 0 has
511 // assetsWithdrawn == assetsTotal > assetsAvailable, which the
512 // insufficient-funds guard above already rejected.
513 if (*lossUnrealized != beast::kZero)
514 {
515 // LCOV_EXCL_START
516 UNREACHABLE(
517 "xrpl::VaultWithdraw::doApply : final withdrawal with non-zero unrealized loss");
518 JLOG(j_.fatal())
519 << "VaultWithdraw: " //
520 "Cannot burn all outstanding shares while unrealized loss is non-zero";
521 return tefINTERNAL;
522 // LCOV_EXCL_STOP
523 }
524
525 STAmount const allAvailable{vaultAsset, *assetsAvailable};
526 if (assetsWithdrawn != allAvailable)
527 {
528 JLOG(j_.error()) //
529 << "VaultWithdraw: final withdrawal share-value mismatch;"
530 << " computed=" << assetsWithdrawn.getText()
531 << " assetsAvailable=" << allAvailable.getText();
532 }
533 assetsWithdrawn = allAvailable;
534
535 // Do not let dust accumulate in the Vault.
536 assetsTotal = 0;
537 assetsAvailable = 0;
538 }
539 else
540 {
541 // Debit both rails by the same delta so sfAssetsTotal and sfAssetsAvailable stay in step,
542 // as required by the ValidVault invariant.
543 assetsTotal -= assetsWithdrawn;
544 assetsAvailable -= assetsWithdrawn;
545 }
546 view().update(vault);
547
548 auto const& vaultAccount = vault->at(sfAccount);
549
550 // Transfer shares from depositor to vault.
551 if (auto const ter = accountSend(
552 view(), accountID_, vaultAccount, sharesRedeemed, j_, {}, WaiveTransferFee::Yes);
553 !isTesSuccess(ter))
554 return ter;
555
556 // Try to remove MPToken for shares, if the account balance is zero. Vault
557 // pseudo-account will never set lsfMPTAuthorized, so we ignore flags.
558 // Keep MPToken if holder is the vault owner.
559 if (accountID_ != vault->at(sfOwner))
560 {
561 if (auto const ter =
562 removeEmptyHolding(applyViewContext, accountID_, sharesRedeemed.asset(), j_);
563 isTesSuccess(ter))
564 {
565 JLOG(j_.debug()) //
566 << "VaultWithdraw: removed empty MPToken for vault shares"
567 << " MPTID=" << to_string(mptIssuanceID) //
568 << " account=" << toBase58(accountID_);
569 }
570 else if (ter != tecHAS_OBLIGATIONS)
571 {
572 // LCOV_EXCL_START
573 JLOG(j_.error()) //
574 << "VaultWithdraw: failed to remove MPToken for vault shares"
575 << " MPTID=" << to_string(mptIssuanceID) //
576 << " account=" << toBase58(accountID_) //
577 << " with result: " << transToken(ter);
578 return ter;
579 // LCOV_EXCL_STOP
580 }
581 // else quietly ignore, account balance is not zero
582 }
583
584 associateAsset(*vault, vaultAsset);
585
586 auto const dstAcct = ctx_.tx[~sfDestination].value_or(accountID_);
587 return doWithdraw(
588 applyViewContext, accountID_, dstAcct, vaultAccount, preFeeBalance_, assetsWithdrawn, j_);
589}
590
591void
593{
594 // No transaction-specific invariants yet (future work).
595}
596
597bool
599 STTx const&,
600 TER,
601 XRPAmount,
602 ReadView const&,
603 beast::Journal const&)
604{
605 // No transaction-specific invariants yet (future work).
606 return true;
607}
608
609} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
Stream error() const
Definition Journal.h:362
Stream debug() const
Definition Journal.h:344
virtual SLE::pointer peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void update(SLE::Ref sle)=0
Indicate changes to a peeked SLE.
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
std::string getText() const override
Definition STAmount.cpp:647
Asset const & asset() const
Definition STAmount.h:496
std::shared_ptr< STLedgerEntry const > const & ConstRef
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:464
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
static bool checkExtraFeatures(PreflightContext const &ctx)
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
void visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override
Inspect a single ledger entry modified by this transaction.
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
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)
Keylet vault(AccountID const &owner, SeqProxy const &seq) noexcept
Definition Indexes.cpp:591
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:567
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::optional< STAmount > sharesToAssetsWithdraw(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount const &shares, WaiveUnrealizedLoss waive=WaiveUnrealizedLoss::No)
From the perspective of a vault, return the number of assets to give the depositor when they redeem a...
bool isSoleShareholder(ReadView const &view, AccountID const &account, SLE::ConstRef issuance)
Returns true iff account holds all of the vault's outstanding shares — i.e.
bool debitIsNonZeroDust(Asset const &asset, Number const &total, Number const &amount)
Returns true if debiting amount from total (the current value of a vault's sfAssetsTotal or sfAssetsA...
TER checkVaultDomain(ReadView const &view, SLE::ConstRef issuance, AccountID const &subject, SuppressExpired suppressExpired)
Checks that subject belongs to the permissioned domain governing a vault's shares.
std::optional< STAmount > assetsToSharesWithdraw(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount const &assets, TruncateShares truncate=TruncateShares::No, WaiveUnrealizedLoss waive=WaiveUnrealizedLoss::No)
From the perspective of a vault, return the number of shares to demand from the depositor when they a...
TER removeEmptyHolding(ApplyViewContext ctx, AccountID const &accountID, MPTIssue const &mptIssue, beast::Journal journal)
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
@ tefINTERNAL
Definition TER.h:168
Number assetsTotalForWithdrawal(SLE::ConstRef vault, WaiveUnrealizedLoss waive)
Returns the assets backing outstanding shares for a withdrawal: sfAssetsTotal minus sfLossUnrealized,...
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
std::expected< STAmount, TER > clampToAssetsTotalScale(SLE::ConstRef vault, STAmount const &delta)
Adjusts a requested asset change (delta) to match the decimal scale of the updated total vault assets...
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.
std::string transToken(TER code)
Definition TER.cpp:257
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
TER doWithdraw(ApplyViewContext ctx, AccountID const &senderAcct, AccountID const &dstAcct, AccountID const &sourceAcct, XRPAmount priorBalance, STAmount const &amount, beast::Journal j)
Definition View.cpp:507
TER canAddHolding(ReadView const &view, MPTIssue const &mptIssue)
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.
TER canWithdraw(ReadView const &view, AccountID const &from, AccountID const &to, SLE::ConstRef toSle, STAmount const &amount, bool hasDestinationTag, std::optional< std::vector< UInt256 > > const &credentialIDs=std::nullopt)
Checks that can withdraw funds from an object to itself or a destination.
Definition View.cpp:437
VaultPhase getVaultPhase(ReadView const &view, SLE::ConstRef vault)
Returns the current lifecycle phase of a vault.
static WaiveUnrealizedLoss shouldWaiveWithdrawal(ReadView const &view, AccountID const &account, SLE::ConstRef issuance)
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
@ temMALFORMED
Definition TER.h:75
@ temBAD_AMOUNT
Definition TER.h:77
bool isTesSuccess(TER x) noexcept
Definition TER.h:683
constexpr std::uint8_t kVaultStrategyFirstComeFirstServe
Vault withdrawal policies.
Definition Protocol.h:308
TERSubset< CanCvtToTER > TER
Definition TER.h:654
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.
@ tecWRONG_ASSET
Definition TER.h:368
@ tecPSEUDO_ACCOUNT
Definition TER.h:370
@ tecNO_ENTRY
Definition TER.h:314
@ tecPATH_DRY
Definition TER.h:302
@ tecINTERNAL
Definition TER.h:318
@ tecTOO_SOON
Definition TER.h:326
@ tecINSUFFICIENT_FUNDS
Definition TER.h:333
@ tecPRECISION_LOSS
Definition TER.h:371
@ tecHAS_OBLIGATIONS
Definition TER.h:325
void associateAsset(STLedgerEntry &sle, Asset const &asset)
Associate an Asset with all sMD_NeedsAsset fields in a ledger entry.
TER checkWithdrawFreeze(ReadView const &view, AccountID const &pseudoAcct, AccountID const &submitterAcct, AccountID const &dstAcct, Asset const &asset)
Checks freeze compliance for withdrawing an asset from a pseudo-account (e.g.
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=SpendableHandling::SimpleBalance)
@ tesSUCCESS
Definition TER.h:250
bool holdingExists(ReadView const &view, AccountID const &account, Asset const &asset)
True if the account already holds this asset (or is the issuer / XRP).
WaiveUnrealizedLoss
Controls whether the withdraw conversion helpers (assetsToSharesWithdraw and sharesToAssetsWithdraw) ...
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:92
ReadView const & view
Definition Transactor.h:95
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