mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-26 08:40:56 +00:00
Compare commits
6 Commits
develop
...
tapanito/v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92517efa0b | ||
|
|
d175449eb5 | ||
|
|
0a2377c0f6 | ||
|
|
85cf5574fa | ||
|
|
63aec2b301 | ||
|
|
18b471b43e |
@@ -1,11 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.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 <expected>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -123,4 +128,72 @@ isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref
|
||||
[[nodiscard]] VaultVersion
|
||||
getVaultVersion(SLE::const_ref vault);
|
||||
|
||||
/**
|
||||
* The single owner of every write to a Vault's two accounting fields,
|
||||
* sfAssetsAvailable and sfAssetsTotal, for a cash-moving-in operation
|
||||
* (deposit, repayment, or default settlement).
|
||||
*
|
||||
* On this branch this is a pure bookkeeping refactor with no behaviour
|
||||
* change: it performs exactly
|
||||
* sfAssetsAvailable += cashIn
|
||||
* sfAssetsTotal += recognitionDelta
|
||||
* view.update(vault)
|
||||
* and nothing else. In particular it does NOT enforce
|
||||
* sfAssetsAvailable <= sfAssetsTotal — callers keep their own guard exactly
|
||||
* where it is today, because that guard is order-sensitive and, at at least
|
||||
* one call site (LoanManage::defaultLoan), the fields legitimately cross
|
||||
* transiently before an existing post-write correction runs.
|
||||
*
|
||||
* @param view The ApplyView to mutate.
|
||||
* @param vault The vault SLE (mutated in place; caller retains ownership).
|
||||
* @param cashIn The amount of cash arriving at the vault's main custody.
|
||||
* Must already be rounded to whatever scale the caller's
|
||||
* transactor uses; this function performs no rounding.
|
||||
* @param recognitionDelta The signed amount sfAssetsTotal should recognise,
|
||||
* independently of cashIn.
|
||||
* @param j Journal (currently unused; reserved for the dust-mechanism
|
||||
* amendment gate that lands on the solution branches).
|
||||
*
|
||||
* @return What sfAssetsAvailable actually moved by. On this branch that is
|
||||
* always exactly cashIn.
|
||||
*/
|
||||
[[nodiscard]] std::expected<Number, TER>
|
||||
addAssetsToVault(
|
||||
ApplyView& view,
|
||||
SLE::ref vault,
|
||||
Number const& cashIn,
|
||||
Number const& recognitionDelta,
|
||||
beast::Journal j);
|
||||
|
||||
/**
|
||||
* The counterpart of addAssetsToVault for a cash-moving-out operation
|
||||
* (withdrawal, clawback, or loan funding). Performs
|
||||
* sfAssetsAvailable -= cashOut
|
||||
* sfAssetsTotal += recognitionDelta
|
||||
* view.update(vault)
|
||||
* See addAssetsToVault for the guard-placement rationale.
|
||||
*
|
||||
* @return What sfAssetsAvailable actually moved by (as a negative number).
|
||||
* On this branch that is always exactly -cashOut.
|
||||
*/
|
||||
[[nodiscard]] std::expected<Number, TER>
|
||||
removeAssetsFromVault(
|
||||
ApplyView& view,
|
||||
SLE::ref vault,
|
||||
Number const& cashOut,
|
||||
Number const& recognitionDelta,
|
||||
beast::Journal j);
|
||||
|
||||
/**
|
||||
* The terminal-withdrawal entry point: assigns both accounting fields to
|
||||
* zero. This is an assignment, not a delta, which is why it is a separate
|
||||
* function rather than a degenerate call to removeAssetsFromVault
|
||||
* (VaultWithdraw.cpp's "Do not let dust accumulate in the Vault" branch).
|
||||
*
|
||||
* @return sfAssetsAvailable's value immediately before it was zeroed (i.e.
|
||||
* what the caller still owes the departing shareholder).
|
||||
*/
|
||||
[[nodiscard]] std::expected<Number, TER>
|
||||
closeVaultAssets(ApplyView& view, SLE::ref vault, beast::Journal j);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include <xrpl/ledger/helpers/VaultHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -11,8 +13,10 @@
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
@@ -157,4 +161,55 @@ getVaultVersion(SLE::const_ref vault)
|
||||
return static_cast<VaultVersion>(version);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<Number, TER>
|
||||
addAssetsToVault(
|
||||
ApplyView& view,
|
||||
SLE::ref vault,
|
||||
Number const& cashIn,
|
||||
Number const& recognitionDelta,
|
||||
beast::Journal j)
|
||||
{
|
||||
(void)j;
|
||||
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::addAssetsToVault : valid Vault sle");
|
||||
|
||||
vault->at(sfAssetsAvailable) += cashIn;
|
||||
vault->at(sfAssetsTotal) += recognitionDelta;
|
||||
view.update(vault);
|
||||
|
||||
return cashIn;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<Number, TER>
|
||||
removeAssetsFromVault(
|
||||
ApplyView& view,
|
||||
SLE::ref vault,
|
||||
Number const& cashOut,
|
||||
Number const& recognitionDelta,
|
||||
beast::Journal j)
|
||||
{
|
||||
(void)j;
|
||||
XRPL_ASSERT(
|
||||
vault && vault->getType() == ltVAULT, "xrpl::removeAssetsFromVault : valid Vault sle");
|
||||
|
||||
vault->at(sfAssetsAvailable) -= cashOut;
|
||||
vault->at(sfAssetsTotal) += recognitionDelta;
|
||||
view.update(vault);
|
||||
|
||||
return -cashOut;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<Number, TER>
|
||||
closeVaultAssets(ApplyView& view, SLE::ref vault, beast::Journal j)
|
||||
{
|
||||
(void)j;
|
||||
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::closeVaultAssets : valid Vault sle");
|
||||
|
||||
Number const assetsAvailable = vault->at(sfAssetsAvailable);
|
||||
vault->at(sfAssetsTotal) = Number(0);
|
||||
vault->at(sfAssetsAvailable) = Number(0);
|
||||
view.update(vault);
|
||||
|
||||
return assetsAvailable;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/LendingHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/VaultHelpers.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -189,10 +190,19 @@ LoanManage::defaultLoan(
|
||||
|
||||
auto const vaultDefaultRounded = roundToAsset(
|
||||
vaultAsset, vaultDefaultAmount, vaultScale, Number::RoundingMode::Downward);
|
||||
vaultTotalProxy -= vaultDefaultRounded;
|
||||
// Increase the Asset Available of the Vault by liquidated First-Loss
|
||||
// Capital and any unclaimed funds amount:
|
||||
vaultAvailableProxy += defaultCovered;
|
||||
// Capital and any unclaimed funds amount, and decrease the Total
|
||||
// Value by the (rounded) default amount. The snap-to-match
|
||||
// workaround and the tecINTERNAL guard immediately below MUST stay
|
||||
// here, after the helper call, re-reading the fields it wrote —
|
||||
// see VaultHelpers.h's addAssetsToVault doc comment and base-branch
|
||||
// plan §5.2: this is the one call site where AssetsAvailable can
|
||||
// transiently exceed AssetsTotal, and an in-helper guard would fire
|
||||
// before this code gets a chance to repair it.
|
||||
if (auto const result =
|
||||
addAssetsToVault(view, vaultSle, defaultCovered, -vaultDefaultRounded, j);
|
||||
!result)
|
||||
return result.error(); // LCOV_EXCL_LINE
|
||||
if (*vaultAvailableProxy > *vaultTotalProxy && !vaultAsset.integral())
|
||||
{
|
||||
auto const difference = vaultAvailableProxy - vaultTotalProxy;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/LendingHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/VaultHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
@@ -485,8 +486,12 @@ LoanPay::doApply()
|
||||
}
|
||||
#endif
|
||||
|
||||
assetsAvailableProxy += totalPaidToVaultRounded;
|
||||
assetsTotalProxy += assetsTotalDelta;
|
||||
// NOTE: totalPaidToVaultRounded, not totalPaidToVaultRaw — the switch to
|
||||
// raw belongs to the dust-mechanism amendment, not this refactor.
|
||||
if (auto const result =
|
||||
addAssetsToVault(view, vaultSle, totalPaidToVaultRounded, assetsTotalDelta, j_);
|
||||
!result)
|
||||
return result.error(); // LCOV_EXCL_LINE
|
||||
|
||||
XRPL_ASSERT_PARTS(
|
||||
*assetsAvailableProxy <= *assetsTotalProxy,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <xrpl/ledger/helpers/LendingHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/VaultHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -634,13 +635,14 @@ LoanSet::doApply()
|
||||
view.insert(loan);
|
||||
|
||||
// Update the balances in the vault
|
||||
vaultAvailableProxy -= principalRequested;
|
||||
vaultTotalProxy += assetsTotalDelta;
|
||||
if (auto const result =
|
||||
removeAssetsFromVault(view, vaultSle, principalRequested, assetsTotalDelta, j_);
|
||||
!result)
|
||||
return result.error(); // LCOV_EXCL_LINE
|
||||
XRPL_ASSERT_PARTS(
|
||||
*vaultAvailableProxy <= *vaultTotalProxy,
|
||||
"xrpl::LoanSet::doApply",
|
||||
"assets available must not be greater than assets outstanding");
|
||||
view.update(vaultSle);
|
||||
|
||||
// Update the balances in the loan broker
|
||||
adjustImpreciseNumber(brokerSle->at(sfDebtTotal), debtTotalDelta, vaultAsset, vaultScale);
|
||||
|
||||
@@ -383,9 +383,10 @@ VaultClawback::doApply()
|
||||
if (sharesDestroyed == beast::kZero)
|
||||
return tecPRECISION_LOSS;
|
||||
|
||||
assetsTotal -= assetsRecovered;
|
||||
assetsAvailable -= assetsRecovered;
|
||||
view().update(vault);
|
||||
if (auto const result =
|
||||
removeAssetsFromVault(view(), vault, assetsRecovered, -assetsRecovered, j_);
|
||||
!result)
|
||||
return result.error(); // LCOV_EXCL_LINE
|
||||
|
||||
auto const& vaultAccount = vault->at(sfAccount);
|
||||
// Transfer shares from holder to vault.
|
||||
|
||||
@@ -222,6 +222,11 @@ VaultCreate::doApply()
|
||||
vault->at(sfSequence) = sequence;
|
||||
vault->at(sfOwner) = accountID_;
|
||||
vault->at(sfAccount) = pseudoId;
|
||||
// Deliberate exception to "every write to sfAssetsTotal /
|
||||
// sfAssetsAvailable goes through VaultHelpers.h's addAssetsToVault /
|
||||
// removeAssetsFromVault / closeVaultAssets" (base-branch plan §5.5):
|
||||
// these are literal zero initializations on a brand-new object, with no
|
||||
// rounding discipline to get wrong.
|
||||
vault->at(sfAssetsTotal) = Number(0);
|
||||
vault->at(sfAssetsAvailable) = Number(0);
|
||||
vault->at(sfLossUnrealized) = Number(0);
|
||||
|
||||
@@ -314,9 +314,9 @@ VaultDeposit::doApply()
|
||||
sharesCreated.asset() != assetsDeposited.asset(),
|
||||
"xrpl::VaultDeposit::doApply : assets are not shares");
|
||||
|
||||
vault->at(sfAssetsTotal) += assetsDeposited;
|
||||
vault->at(sfAssetsAvailable) += assetsDeposited;
|
||||
view().update(vault);
|
||||
if (auto const result = addAssetsToVault(view(), vault, assetsDeposited, assetsDeposited, j_);
|
||||
!result)
|
||||
return result.error(); // LCOV_EXCL_LINE
|
||||
|
||||
// A deposit must not push the vault over its limit.
|
||||
auto const maximum = *vault->at(sfAssetsMaximum);
|
||||
|
||||
@@ -339,15 +339,16 @@ VaultWithdraw::doApply()
|
||||
assetsWithdrawn = allAvailable;
|
||||
|
||||
// Do not let dust accumulate in the Vault.
|
||||
assetsTotal = 0;
|
||||
assetsAvailable = 0;
|
||||
if (auto const result = closeVaultAssets(view(), vault, j_); !result)
|
||||
return result.error(); // LCOV_EXCL_LINE
|
||||
}
|
||||
else
|
||||
{
|
||||
assetsTotal -= assetsWithdrawn;
|
||||
assetsAvailable -= assetsWithdrawn;
|
||||
if (auto const result =
|
||||
removeAssetsFromVault(view(), vault, assetsWithdrawn, -assetsWithdrawn, j_);
|
||||
!result)
|
||||
return result.error(); // LCOV_EXCL_LINE
|
||||
}
|
||||
view().update(vault);
|
||||
|
||||
auto const& vaultAccount = vault->at(sfAccount);
|
||||
|
||||
|
||||
54
src/test/app/lending/VaultDustProbe.h
Normal file
54
src/test/app/lending/VaultDustProbe.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
// Vault Dust — the probe seam.
|
||||
//
|
||||
// Where a Vault's un-recognized remainder ("dust") is stored differs per
|
||||
// implementation:
|
||||
// - the base branch has no dust mechanism at all;
|
||||
// - solution A (…-pseudo-account) stores it as the balance of a second
|
||||
// pseudo-account;
|
||||
// - solution B' (…-trustline-dust) stores it as a signed field beside the
|
||||
// balance on the Vault's custody trust line.
|
||||
//
|
||||
// This header is the ONLY place the shared test suite
|
||||
// (src/test/app/lending/VaultRounding_test.cpp) is allowed to know about
|
||||
// that difference, and it is the ONLY file that may differ between the two
|
||||
// solution branches. Do not reference sfDustAccount, sfDust, or any other
|
||||
// implementation-specific field anywhere else in a test — route every such
|
||||
// read through readVaultDust() below.
|
||||
//
|
||||
// readVaultDust() below is the whole seam: each solution branch reimplements
|
||||
// its body and changes nothing else. There is deliberately no "does this
|
||||
// build have a reservoir?" flag — the shared suite asserts the post-fix
|
||||
// oracles unconditionally, so on the base branch it fails, and that failure
|
||||
// is the bug's demonstration (see the RED/GREEN CONTRACT note in
|
||||
// VaultRounding_test.cpp).
|
||||
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// Per-Vault: how much dust does this Vault currently hold, normalized to
|
||||
// Vault-pseudo-account terms (i.e. a positive Number means "the Vault is
|
||||
// carrying this much unrecognized value on the borrower's behalf").
|
||||
//
|
||||
// Returns zero when the build has no reservoir (this branch), and also when
|
||||
// this particular Vault has none.
|
||||
//
|
||||
// `inline` is not optional: this header is included both by the shared
|
||||
// suite and by each branch's own per-solution test file, so a non-inline
|
||||
// definition would be a duplicate-symbol link error. If a solution's
|
||||
// implementation grows past a few lines, move the body to a
|
||||
// VaultDustProbe.cpp beside this header rather than dropping `inline`.
|
||||
[[nodiscard]] inline Number
|
||||
readVaultDust(jtx::Env const& env, Keylet const& vaultKeylet)
|
||||
{
|
||||
(void)env;
|
||||
(void)vaultKeylet;
|
||||
return Number{};
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
1030
src/test/app/lending/VaultRounding_test.cpp
Normal file
1030
src/test/app/lending/VaultRounding_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user