mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-22 06:40:53 +00:00
feat: Centralize Vault balance mutations in VaultHelpers
Add addVaultAssets, removeVaultAssets (two overloads: plain accountSend and doWithdraw-based), clawbackVaultAssets, and moveVaultAssets as the single points through which a Vault's sfAssetsTotal/sfAssetsAvailable are mutated and funds move to/from its pseudo-account: - addVaultAssets increases both fields and transfers in from a sender. - removeVaultAssets/clawbackVaultAssets decrease both fields equally and transfer out; a FinalRemoval flag hard-resets both fields to exactly zero on a Vault's last withdrawal, since the discounted exchange-rate formula can produce values with more precision than the asset can canonically represent, and subtracting such a value would leave a non-canonical residual instead of an exact zero. - moveVaultAssets decreases only sfAssetsAvailable, for disbursements (e.g. a loan's principal and origination fee) where sfAssetsTotal independently grows via accrued interest. Also consolidate getAssetsTotalScale into VaultHelpers::getVaultScale, and move isRounded from LendingHelpers into STAmount.h alongside the other rounding utilities.
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/VaultHelpers.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
@@ -216,14 +217,6 @@ adjustImpreciseNumber(
|
||||
value = 0;
|
||||
}
|
||||
|
||||
inline int
|
||||
getAssetsTotalScale(SLE::const_ref vaultSle)
|
||||
{
|
||||
if (!vaultSle)
|
||||
return Number::kMinExponent - 1; // LCOV_EXCL_LINE
|
||||
return scale(vaultSle->at(sfAssetsTotal), vaultSle->at(sfAsset));
|
||||
}
|
||||
|
||||
// Compute the minimum required broker cover, rounded consistently.
|
||||
// DebtTotal is a broker-level aggregate maintained at vault scale, so the
|
||||
// rounding must also use vault scale — never an individual loan's scale.
|
||||
@@ -236,7 +229,7 @@ minimumBrokerCover(Number const& debtTotal, TenthBips32 coverRateMinimum, SLE::c
|
||||
return roundToAsset(
|
||||
vaultSle->at(sfAsset),
|
||||
tenthBipsOfValue(debtTotal, coverRateMinimum),
|
||||
getAssetsTotalScale(vaultSle));
|
||||
getVaultScale(vaultSle));
|
||||
}
|
||||
|
||||
TER
|
||||
@@ -610,9 +603,6 @@ computeLoanProperties(
|
||||
TenthBips32 managementFeeRate,
|
||||
std::int32_t minimumScale);
|
||||
|
||||
bool
|
||||
isRounded(Asset const& asset, Number const& value, std::int32_t scale);
|
||||
|
||||
// Indicates what type of payment is being made.
|
||||
// regular, late, and full are mutually exclusive.
|
||||
// overpayment is an "add on" to a regular payment, and follows that path with
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
@@ -123,4 +128,182 @@ isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref
|
||||
[[nodiscard]] VaultVersion
|
||||
getVaultVersion(SLE::const_ref vault);
|
||||
|
||||
/**
|
||||
* Returns the scale (number of decimal places) at which a vault's
|
||||
* sfAssetsTotal is maintained, derived from the vault's asset and its
|
||||
* current sfAssetsTotal value.
|
||||
*
|
||||
* @param vault The vault SLE.
|
||||
*
|
||||
* @return The vault's scale, or `Number::kMinExponent - 1` if `vault` is
|
||||
* null.
|
||||
*/
|
||||
[[nodiscard]] int
|
||||
getVaultScale(SLE::const_ref vault);
|
||||
|
||||
/**
|
||||
* The single point through which assets are added to a Vault: updates the
|
||||
* Vault's sfAssetsTotal and sfAssetsAvailable and transfers `amount` of the
|
||||
* Vault's asset from `sender` to the Vault's pseudo-account.
|
||||
*
|
||||
* Callers are responsible for rounding `amount` and `valueDelta` to whatever
|
||||
* scale is appropriate for their own accounting (e.g. current vs. posterior
|
||||
* Vault scale); this helper does not perform any additional rounding.
|
||||
*
|
||||
* @param view The ledger view to apply changes to.
|
||||
* @param vault The vault SLE. Must not be null.
|
||||
* @param sender The account to transfer `amount` from.
|
||||
* @param amount The amount to add to sfAssetsAvailable, and to transfer from
|
||||
* `sender` to the Vault's pseudo-account.
|
||||
* @param valueDelta The amount to add to sfAssetsTotal. May differ from
|
||||
* `amount`, e.g. when recognizing a value change that is
|
||||
* not fully backed by a matching cash transfer. May be
|
||||
* negative (e.g. a default write-off, or a small rounding
|
||||
* correction), unlike `amount`.
|
||||
* @param j Journal for logging.
|
||||
*
|
||||
* @return TER on success or failure.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
addVaultAssets(
|
||||
ApplyView& view,
|
||||
SLE::ref vault,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
STAmount const& valueDelta,
|
||||
beast::Journal j);
|
||||
|
||||
/**
|
||||
* Signals that a removal is the last one possible for a Vault — i.e. it
|
||||
* burns every outstanding share. removeVaultAssets uses this to hard-reset
|
||||
* sfAssetsTotal and sfAssetsAvailable to exactly zero, rather than
|
||||
* subtracting `amount`/`valueDelta` from them.
|
||||
*
|
||||
* This matters because the discounted exchange-rate formula used to compute
|
||||
* a withdrawal's `amount` can produce a value with more decimal precision
|
||||
* than the Vault's asset can canonically represent. Subtracting such a
|
||||
* value from the field would leave a non-canonical residual instead of an
|
||||
* exact zero, corrupting the ledger entry. A final removal is defined to
|
||||
* exhaust the Vault's exposure entirely, so hard-resetting to zero is both
|
||||
* simpler and correct — no residual dust is possible or desired.
|
||||
*/
|
||||
enum class FinalRemoval : bool { No = false, Yes = true };
|
||||
|
||||
/**
|
||||
* The single point through which assets are clawed back from a Vault entirely:
|
||||
* decreases the Vault's sfAssetsTotal and sfAssetsAvailable
|
||||
* and transfers @p amount from the Vault's pseudo-account to @p recipient via
|
||||
* a plain accountSend.
|
||||
*
|
||||
* Callers are responsible for rounding @p amount to whatever
|
||||
* scale is appropriate for their own accounting; this helper does not
|
||||
* perform any additional rounding.
|
||||
*
|
||||
* @param view The ledger view to apply changes to.
|
||||
* @param vault The vault SLE. Must not be null.
|
||||
* @param recipient The account to transfer `amount` to. Must already be
|
||||
* able to hold the Vault's asset without further setup.
|
||||
* @param amount The amount to clawback from the vault and transfer from the
|
||||
* Vault's pseudo-account to `recipient`. Must be positive;
|
||||
* callers must skip calling this helper entirely for a
|
||||
* zero-amount clawback (unlike addVaultAssets/removeVaultAssets,
|
||||
* which tolerate a zero `amount`).
|
||||
* @param j Journal for logging.
|
||||
*
|
||||
* @return TER code.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
clawbackVaultAssets(
|
||||
ApplyView& view,
|
||||
SLE::ref vault,
|
||||
AccountID const& recipient,
|
||||
STAmount const& amount,
|
||||
beast::Journal j);
|
||||
|
||||
/**
|
||||
* The single point through which assets are removed from a Vault entirely
|
||||
* and withdrawn to a recipient that may not yet have a holding for the
|
||||
* Vault's asset: decreases the Vault's sfAssetsTotal and sfAssetsAvailable
|
||||
* and calls doWithdraw to transfer @p amount from the
|
||||
* Vault's pseudo-account to @p dstAcct.
|
||||
*
|
||||
* Unlike clawbackVaultAssets, this relies solely on doWithdraw's own
|
||||
* pre-transfer balance check rather than an additional post-transfer sanity
|
||||
* check.
|
||||
*
|
||||
* Callers are responsible for rounding @p amount; this helper does not
|
||||
* perform any additional rounding, except when `finalRemoval` is Yes (see
|
||||
* FinalRemoval).
|
||||
*
|
||||
* @param ctx The apply-view context to apply changes to.
|
||||
* @param vault The vault SLE. Must not be null.
|
||||
* @param senderAcct The account that submitted the withdrawal transaction.
|
||||
* @param dstAcct The account to transfer `amount` to; may equal `senderAcct`.
|
||||
* @param priorBalance The XRP reserve base, passed through to doWithdraw for
|
||||
* creating a holding for `dstAcct` when required.
|
||||
* @param amount The amount to subtract from sfAssetsAvailable, and to
|
||||
* transfer from the Vault's pseudo-account to `dstAcct`.
|
||||
* Ignored (other than for the transfer) when `finalRemoval` is
|
||||
* Yes.
|
||||
* @param j Journal for logging.
|
||||
* @param finalRemoval Whether this is the Vault's final removal (see
|
||||
* FinalRemoval).
|
||||
*
|
||||
* @return TER from doWithdraw.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
removeVaultAssets(
|
||||
ApplyViewContext ctx,
|
||||
SLE::ref vault,
|
||||
AccountID const& senderAcct,
|
||||
AccountID const& dstAcct,
|
||||
XRPAmount priorBalance,
|
||||
STAmount const& amount,
|
||||
beast::Journal j,
|
||||
FinalRemoval finalRemoval = FinalRemoval::No);
|
||||
|
||||
/**
|
||||
* The single point through which cash is moved out of a Vault's
|
||||
* sfAssetsAvailable to multiple recipients in a single atomic payment, e.g.
|
||||
* a loan's principal and origination fee, without necessarily shrinking the
|
||||
* Vault's total exposure: updates sfAssetsAvailable (decreases by the sum of
|
||||
* `recipients`' amounts) and sfAssetsTotal (changes by `valueDelta`, same
|
||||
* sign convention as addVaultAssets — typically an increase, since
|
||||
* disbursing a loan recognizes accrued interest into sfAssetsTotal even as
|
||||
* cash leaves the Vault), then transfers the Vault's asset from the Vault's
|
||||
* pseudo-account to each of `recipients`, via accountSendMulti.
|
||||
*
|
||||
* Unlike removeVaultAssets, this is not a removal — the Vault's receivables
|
||||
* grow to match the cash that leaves sfAssetsAvailable, so there is no
|
||||
* "final" edge case to handle here.
|
||||
*
|
||||
* Recipients must already be able to hold the Vault's asset (e.g. via
|
||||
* addEmptyHolding and requireAuth performed by the caller beforehand); this
|
||||
* helper does not create holdings or check authorization.
|
||||
*
|
||||
* sfAssetsAvailable is decreased by an STAmount built from the sum of the
|
||||
* recipients' Numbers, so for a very large recipient list whose sum exceeds
|
||||
* STAmount's ~16 significant digits, this could round differently than
|
||||
* summing the underlying Numbers directly. Not a concern for the current
|
||||
* caller (LoanSet, two recipients).
|
||||
*
|
||||
* @param view The ledger view to apply changes to.
|
||||
* @param vault The vault SLE. Must not be null.
|
||||
* @param recipients The accounts and amounts to transfer from the Vault's
|
||||
* pseudo-account. Must contain more than one entry.
|
||||
* @param valueDelta The amount to add to sfAssetsTotal (same convention as
|
||||
* addVaultAssets). May be negative, and may differ from
|
||||
* the sum of `recipients`' amounts.
|
||||
* @param j Journal for logging.
|
||||
*
|
||||
* @return TER from accountSendMulti.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
moveVaultAssets(
|
||||
ApplyView& view,
|
||||
SLE::ref vault,
|
||||
MultiplePaymentDestinations const& recipients,
|
||||
STAmount const& valueDelta,
|
||||
beast::Journal j);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -772,6 +772,13 @@ roundToAsset(
|
||||
return roundToScale(ret, scale);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isRounded(Asset const& asset, Number const& value, std::int32_t scale)
|
||||
{
|
||||
return roundToAsset(asset, value, scale, Number::RoundingMode::Downward) ==
|
||||
roundToAsset(asset, value, scale, Number::RoundingMode::Upward);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
inline bool
|
||||
|
||||
@@ -120,17 +120,6 @@ loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval)
|
||||
return tenthBipsOfValue(Number(paymentInterval), interestRate) / kSecondsInYear;
|
||||
}
|
||||
|
||||
/* Checks if a value is already rounded to the specified scale.
|
||||
* Returns true if rounding down and rounding up produce the same result,
|
||||
* indicating no further precision exists beyond the scale.
|
||||
*/
|
||||
bool
|
||||
isRounded(Asset const& asset, Number const& value, std::int32_t scale)
|
||||
{
|
||||
return roundToAsset(asset, value, scale, Number::RoundingMode::Downward) ==
|
||||
roundToAsset(asset, value, scale, Number::RoundingMode::Upward);
|
||||
}
|
||||
|
||||
namespace accrual {
|
||||
|
||||
AccountingDeltas
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
#include <xrpl/ledger/helpers/VaultHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
@@ -11,6 +18,8 @@
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
@@ -157,4 +166,193 @@ getVaultVersion(SLE::const_ref vault)
|
||||
return static_cast<VaultVersion>(version);
|
||||
}
|
||||
|
||||
[[nodiscard]] int
|
||||
getVaultScale(SLE::const_ref vault)
|
||||
{
|
||||
if (!vault)
|
||||
return Number::kMinExponent - 1; // LCOV_EXCL_LINE
|
||||
return scale(vault->at(sfAssetsTotal), vault->at(sfAsset));
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
addVaultAssets(
|
||||
ApplyView& view,
|
||||
SLE::ref vault,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
STAmount const& valueDelta,
|
||||
beast::Journal j)
|
||||
{
|
||||
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::addVaultAssets : valid Vault sle");
|
||||
|
||||
Asset const asset = vault->at(sfAsset);
|
||||
XRPL_ASSERT(amount.asset() == asset, "xrpl::addVaultAssets : amount matches vault asset");
|
||||
XRPL_ASSERT(
|
||||
valueDelta.asset() == asset, "xrpl::addVaultAssets : valueDelta matches vault asset");
|
||||
|
||||
XRPL_ASSERT(amount >= beast::kZero, "xrpl::addVaultAssets : amount is non-negative");
|
||||
|
||||
// Callers are responsible for rounding amount/valueDelta to whatever
|
||||
// scale their own accounting requires; this helper does not re-round.
|
||||
// valueDelta and amount are independent (e.g. a loan default written off
|
||||
// entirely by the vault, with no first-loss capital cover, has a nonzero
|
||||
// (and possibly negative) valueDelta but a zero amount; late/regular loan
|
||||
// payments can also carry a small negative valueDelta from untracked
|
||||
// interest rounding corrections), so both fields are always updated even
|
||||
// when there is nothing to transfer.
|
||||
vault->at(sfAssetsTotal) += valueDelta;
|
||||
vault->at(sfAssetsAvailable) += amount;
|
||||
view.update(vault);
|
||||
|
||||
if (auto const ter =
|
||||
accountSend(view, sender, vault->at(sfAccount), amount, j, {}, WaiveTransferFee::Yes);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Applies a full-removal mutation to the Vault's ledger fields: both
|
||||
// callers (clawbackVaultAssets and removeVaultAssets) apply `amount` to
|
||||
// sfAssetsTotal and sfAssetsAvailable equally (unlike
|
||||
// addVaultAssets/moveVaultAssets, a full removal always shrinks both fields
|
||||
// by the same amount). On a final removal, both fields are hard-reset to
|
||||
// exactly zero rather than computed via subtraction: see FinalRemoval's
|
||||
// doc comment for why an arithmetic subtraction cannot be trusted to land
|
||||
// on exactly zero here.
|
||||
void
|
||||
applyRemoveVaultAssets(
|
||||
ApplyView& view,
|
||||
SLE::ref vault,
|
||||
STAmount const& amount,
|
||||
FinalRemoval finalRemoval)
|
||||
{
|
||||
if (finalRemoval == FinalRemoval::Yes)
|
||||
{
|
||||
vault->at(sfAssetsTotal) = 0;
|
||||
vault->at(sfAssetsAvailable) = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
vault->at(sfAssetsTotal) -= amount;
|
||||
vault->at(sfAssetsAvailable) -= amount;
|
||||
}
|
||||
view.update(vault);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
[[nodiscard]] TER
|
||||
clawbackVaultAssets(
|
||||
ApplyView& view,
|
||||
SLE::ref vault,
|
||||
AccountID const& recipient,
|
||||
STAmount const& amount,
|
||||
beast::Journal j)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
vault && vault->getType() == ltVAULT, "xrpl::clawbackVaultAssets : valid Vault sle");
|
||||
|
||||
Asset const asset = vault->at(sfAsset);
|
||||
XRPL_ASSERT(amount.asset() == asset, "xrpl::clawbackVaultAssets : amount matches vault asset");
|
||||
XRPL_ASSERT(amount > beast::kZero, "xrpl::clawbackVaultAssets : amount is positive");
|
||||
|
||||
if (amount > *vault->at(sfAssetsAvailable))
|
||||
return tefINTERNAL;
|
||||
|
||||
applyRemoveVaultAssets(view, vault, amount, FinalRemoval::No);
|
||||
|
||||
if (auto const ter = accountSend(
|
||||
view, vault->at(sfAccount), recipient, amount, j, {}, WaiveTransferFee::Yes);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// Sanity check
|
||||
if (accountHolds(
|
||||
view,
|
||||
vault->at(sfAccount),
|
||||
asset,
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
j) < beast::kZero)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "clawbackVaultAssets: negative balance of vault assets.";
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeVaultAssets(
|
||||
ApplyViewContext ctx,
|
||||
SLE::ref vault,
|
||||
AccountID const& senderAcct,
|
||||
AccountID const& dstAcct,
|
||||
XRPAmount priorBalance,
|
||||
STAmount const& amount,
|
||||
beast::Journal j,
|
||||
FinalRemoval finalRemoval)
|
||||
{
|
||||
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::removeVaultAssets : valid Vault sle");
|
||||
|
||||
Asset const asset = vault->at(sfAsset);
|
||||
XRPL_ASSERT(amount.asset() == asset, "xrpl::removeVaultAssets : amount matches vault asset");
|
||||
XRPL_ASSERT(amount >= beast::kZero, "xrpl::removeVaultAssets : amount is non-negative");
|
||||
|
||||
applyRemoveVaultAssets(ctx.view, vault, amount, finalRemoval);
|
||||
|
||||
if (amount == beast::kZero)
|
||||
return tesSUCCESS;
|
||||
|
||||
return doWithdraw(ctx, senderAcct, dstAcct, vault->at(sfAccount), priorBalance, amount, j);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
moveVaultAssets(
|
||||
ApplyView& view,
|
||||
SLE::ref vault,
|
||||
MultiplePaymentDestinations const& recipients,
|
||||
STAmount const& valueDelta,
|
||||
beast::Journal j)
|
||||
{
|
||||
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::moveVaultAssets : valid Vault sle");
|
||||
XRPL_ASSERT(recipients.size() > 1, "xrpl::moveVaultAssets : multiple recipients provided");
|
||||
|
||||
Asset const asset = vault->at(sfAsset);
|
||||
XRPL_ASSERT(
|
||||
valueDelta.asset() == asset, "xrpl::moveVaultAssets : valueDelta matches vault asset");
|
||||
XRPL_ASSERT(
|
||||
valueDelta == beast::kZero || getVaultVersion(vault) == VaultVersion::Legacy,
|
||||
"xrpl::moveVaultAssets : nonzero valueDelta requires Legacy vault version");
|
||||
|
||||
Number amountTotal{};
|
||||
for (auto const& [recipient, recipientAmount] : recipients)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
recipientAmount >= beast::kZero,
|
||||
"xrpl::moveVaultAssets : recipientAmount is non-negative");
|
||||
amountTotal += recipientAmount;
|
||||
}
|
||||
STAmount const amount{asset, amountTotal};
|
||||
|
||||
// valueDelta follows addVaultAssets's convention (added to sfAssetsTotal):
|
||||
// disbursing a loan typically increases sfAssetsTotal via accrued
|
||||
// interest even as cash leaves sfAssetsAvailable.
|
||||
|
||||
vault->at(sfAssetsTotal) += valueDelta;
|
||||
vault->at(sfAssetsAvailable) -= amount;
|
||||
view.update(vault);
|
||||
|
||||
if (amount == beast::kZero)
|
||||
return tesSUCCESS;
|
||||
|
||||
return accountSendMulti(
|
||||
view, vault->at(sfAccount), asset, recipients, j, WaiveTransferFee::Yes);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
Reference in New Issue
Block a user