mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-11 14:18:07 +00:00
Merge tapanito/sav-fixed-precision-loanset into tapanito/sav-fixed-precision-loanpay
This commit is contained in:
@@ -64,7 +64,7 @@ namespace detail {
|
||||
* unrounded delta.
|
||||
*/
|
||||
[[nodiscard]] int
|
||||
getPosteriorBrokerCoverScale(SLE::ConstRef vault, SLE::ConstRef broker, STAmount const& delta);
|
||||
getPosteriorBrokerCoverScale(SLE::ConstRef vault, SLE::ConstRef broker, Number const& delta);
|
||||
|
||||
/**
|
||||
* Round a cover outflow delta (cover withdraw, cover clawback, the
|
||||
@@ -113,7 +113,7 @@ debitToPosteriorBrokerCoverScale(
|
||||
creditToPosteriorBrokerCoverScale(
|
||||
SLE::ConstRef vault,
|
||||
SLE::ConstRef broker,
|
||||
STAmount const& raw,
|
||||
Number const& raw,
|
||||
Number::RoundingMode roundingMode);
|
||||
|
||||
/**
|
||||
|
||||
@@ -100,16 +100,6 @@ getVaultScale(SLE::ConstRef vault);
|
||||
[[nodiscard]] int
|
||||
getVaultBaseScale(SLE::ConstRef vault);
|
||||
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* Return the Vault's posterior live exponent after applying an unrounded delta.
|
||||
*/
|
||||
[[nodiscard]] int
|
||||
getPosteriorVaultScale(SLE::ConstRef vault, STAmount const& delta);
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Round an amount at the Vault's posterior live exponent.
|
||||
*/
|
||||
@@ -121,6 +111,12 @@ roundToPosteriorVaultScale(
|
||||
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* Return the Vault's posterior live exponent after applying an unrounded delta.
|
||||
*/
|
||||
[[nodiscard]] int
|
||||
getPosteriorVaultScale(SLE::ConstRef vault, STAmount const& delta);
|
||||
|
||||
/**
|
||||
* Shared grid-floor core of getVaultScale / getPosteriorVaultScale /
|
||||
* getPosteriorBrokerCoverScale: baseScale when reference is zero, otherwise
|
||||
@@ -149,7 +145,7 @@ posteriorAssetScale(
|
||||
Asset const& asset,
|
||||
int baseScale,
|
||||
Number const& reference,
|
||||
STAmount const& delta);
|
||||
Number const& delta);
|
||||
|
||||
/**
|
||||
* Shared core of creditToPosteriorAvailableScale and
|
||||
@@ -157,14 +153,15 @@ posteriorAssetScale(
|
||||
* returns the difference from reference, so the delta applied is exactly
|
||||
* what moves reference onto the floored sum. See
|
||||
* creditToPosteriorAvailableScale's doc for why the sum, not raw alone,
|
||||
* must be floored.
|
||||
* must be floored. raw is the exact amount; it is not rounded on its own
|
||||
* before being added to reference.
|
||||
*/
|
||||
[[nodiscard]] STAmount
|
||||
creditToPosteriorScale(
|
||||
Asset const& asset,
|
||||
Number const& reference,
|
||||
int atScale,
|
||||
STAmount const& raw,
|
||||
Number const& raw,
|
||||
Number::RoundingMode roundingMode);
|
||||
|
||||
} // namespace detail
|
||||
@@ -185,11 +182,14 @@ creditToPosteriorScale(
|
||||
* the sum instead, floor16(9999999999.999999 + 0.000011) minus
|
||||
* 9999999999.999999, is exact by construction, since STAmount's own
|
||||
* 16-digit canonical form of the sum is what gets subtracted from.
|
||||
*
|
||||
* raw is taken as an exact Number: rounding it to 16 digits before adding
|
||||
* it to AssetsAvailable could drop a tail that moves the floored sum.
|
||||
*/
|
||||
[[nodiscard]] STAmount
|
||||
creditToPosteriorAvailableScale(
|
||||
SLE::ConstRef vault,
|
||||
STAmount const& raw,
|
||||
Number const& raw,
|
||||
Number::RoundingMode roundingMode);
|
||||
|
||||
/**
|
||||
|
||||
@@ -345,6 +345,12 @@ enum class VaultVersion : uint8_t {
|
||||
FixedPrecision,
|
||||
};
|
||||
|
||||
// Code compares VaultVersion values with < and >= (e.g. "CashBasis or later"),
|
||||
// so each later version must stay numerically larger than the one before.
|
||||
static_assert(
|
||||
VaultVersion::Legacy < VaultVersion::CashBasis &&
|
||||
VaultVersion::CashBasis < VaultVersion::FixedPrecision);
|
||||
|
||||
/**
|
||||
* Vault kind. Distinguishes closed-ended vaults from the default open-ended
|
||||
* kind. Persisted as sfVaultKind (UINT8); absent means OpenEnded.
|
||||
|
||||
@@ -69,7 +69,7 @@ canApplyToBrokerCover(
|
||||
namespace detail {
|
||||
|
||||
[[nodiscard]] int
|
||||
getPosteriorBrokerCoverScale(SLE::ConstRef vault, SLE::ConstRef broker, STAmount const& delta)
|
||||
getPosteriorBrokerCoverScale(SLE::ConstRef vault, SLE::ConstRef broker, Number const& delta)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
vault && vault->getType() == ltVAULT,
|
||||
@@ -77,9 +77,6 @@ getPosteriorBrokerCoverScale(SLE::ConstRef vault, SLE::ConstRef broker, STAmount
|
||||
XRPL_ASSERT(
|
||||
broker && broker->getType() == ltLOAN_BROKER,
|
||||
"xrpl::detail::getPosteriorBrokerCoverScale : valid LoanBroker sle");
|
||||
XRPL_ASSERT(
|
||||
delta.asset() == vault->at(sfAsset),
|
||||
"xrpl::detail::getPosteriorBrokerCoverScale : delta and Vault asset match");
|
||||
|
||||
return posteriorAssetScale(
|
||||
getVaultVersion(vault),
|
||||
@@ -116,7 +113,7 @@ roundToPosteriorBrokerCoverScale(
|
||||
creditToPosteriorBrokerCoverScale(
|
||||
SLE::ConstRef vault,
|
||||
SLE::ConstRef broker,
|
||||
STAmount const& raw,
|
||||
Number const& raw,
|
||||
Number::RoundingMode roundingMode)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
@@ -125,13 +122,13 @@ creditToPosteriorBrokerCoverScale(
|
||||
XRPL_ASSERT(
|
||||
broker && broker->getType() == ltLOAN_BROKER,
|
||||
"xrpl::creditToPosteriorBrokerCoverScale : valid LoanBroker sle");
|
||||
XRPL_ASSERT(
|
||||
raw.asset() == vault->at(sfAsset),
|
||||
"xrpl::creditToPosteriorBrokerCoverScale : raw and Vault asset match");
|
||||
if (raw.integral())
|
||||
return raw;
|
||||
|
||||
Asset const asset = vault->at(sfAsset);
|
||||
if (asset.integral())
|
||||
{
|
||||
NumberRoundModeGuard const rg(roundingMode);
|
||||
return STAmount{asset, raw};
|
||||
}
|
||||
|
||||
Number const reference = broker->at(sfCoverAvailable);
|
||||
int const scale = detail::getPosteriorBrokerCoverScale(vault, broker, raw);
|
||||
return detail::creditToPosteriorScale(asset, reference, scale, raw, roundingMode);
|
||||
@@ -173,8 +170,10 @@ checkOptionalBrokerCoverInflow(SLE::ConstRef vault, SLE::ConstRef broker, STAmou
|
||||
if (getVaultVersion(vault) != VaultVersion::FixedPrecision)
|
||||
return tesSUCCESS;
|
||||
|
||||
STAmount const rounded = detail::roundToPosteriorBrokerCoverScale(
|
||||
vault, broker, amount, Number::RoundingMode::TowardsZero);
|
||||
// amount is the effective credit (already floored on the posterior
|
||||
// CoverAvailable grid by creditToPosteriorBrokerCoverScale); rounding it
|
||||
// again as a standalone delta could hide a crossing.
|
||||
STAmount const& rounded = amount;
|
||||
int const baseScale = getVaultBaseScale(vault);
|
||||
// Keep this explicit even though the Open-limit capacity check below rejects
|
||||
// every coarsening transition too. The protocol defines both conditions
|
||||
|
||||
@@ -48,7 +48,7 @@ fixedBaseScale(SLE::ConstRef vault)
|
||||
// Thin, vault-specific wrapper over posteriorAssetScale: used by
|
||||
// getPosteriorVaultScale and creditToPosteriorAvailableScale.
|
||||
[[nodiscard]] int
|
||||
posteriorScale(SLE::ConstRef vault, Number const& reference, STAmount const& delta)
|
||||
posteriorScale(SLE::ConstRef vault, Number const& reference, Number const& delta)
|
||||
{
|
||||
return detail::posteriorAssetScale(
|
||||
getVaultVersion(vault), vault->at(sfAsset), fixedBaseScale(vault), reference, delta);
|
||||
@@ -156,12 +156,12 @@ posteriorAssetScale(
|
||||
Asset const& asset,
|
||||
int baseScale,
|
||||
Number const& reference,
|
||||
STAmount const& delta)
|
||||
Number const& delta)
|
||||
{
|
||||
Number const posterior = [&] {
|
||||
NumberRoundModeGuard const rg(Number::RoundingMode::ToNearest);
|
||||
return reference + delta;
|
||||
}();
|
||||
// ToNearest for the sum and for scale()'s canonicalization of it, as the
|
||||
// Legacy/CashBasis clamp always did; liveScale sets its own mode.
|
||||
NumberRoundModeGuard const rg(Number::RoundingMode::ToNearest);
|
||||
Number const posterior = reference + delta;
|
||||
|
||||
switch (version)
|
||||
{
|
||||
@@ -182,14 +182,14 @@ creditToPosteriorScale(
|
||||
Asset const& asset,
|
||||
Number const& reference,
|
||||
int atScale,
|
||||
STAmount const& raw,
|
||||
Number const& raw,
|
||||
Number::RoundingMode roundingMode)
|
||||
{
|
||||
// Floor the SUM (reference + raw), not just raw, at atScale. See
|
||||
// creditToPosteriorAvailableScale's doc: a delta floored on its own grid
|
||||
// can still leave a 17-digit sum once the reference has crossed a power
|
||||
// of ten.
|
||||
Number const flooredSum = roundToAsset(asset, reference + Number(raw), atScale, roundingMode);
|
||||
Number const flooredSum = roundToAsset(asset, reference + raw, atScale, roundingMode);
|
||||
// flooredSum - reference can carry 17 significant digits (the sum is on
|
||||
// the posterior grid, the reference on the finer one); build the
|
||||
// STAmount under roundingMode, not the caller's ambient mode, so the
|
||||
@@ -198,6 +198,18 @@ creditToPosteriorScale(
|
||||
return STAmount{asset, flooredSum - reference};
|
||||
}
|
||||
|
||||
[[nodiscard]] int
|
||||
getPosteriorVaultScale(SLE::ConstRef vault, STAmount const& delta)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
vault && vault->getType() == ltVAULT,
|
||||
"xrpl::detail::getPosteriorVaultScale : valid Vault sle");
|
||||
XRPL_ASSERT(
|
||||
delta.asset() == vault->at(sfAsset),
|
||||
"xrpl::detail::getPosteriorVaultScale : delta and Vault asset match");
|
||||
return posteriorScale(vault, getAssetsTotal(vault), delta);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
[[nodiscard]] Number
|
||||
@@ -330,22 +342,6 @@ getVaultBaseScale(SLE::ConstRef vault)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
[[nodiscard]] int
|
||||
getPosteriorVaultScale(SLE::ConstRef vault, STAmount const& delta)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
vault && vault->getType() == ltVAULT,
|
||||
"xrpl::detail::getPosteriorVaultScale : valid Vault sle");
|
||||
XRPL_ASSERT(
|
||||
delta.asset() == vault->at(sfAsset),
|
||||
"xrpl::detail::getPosteriorVaultScale : delta and Vault asset match");
|
||||
return posteriorScale(vault, getAssetsTotal(vault), delta);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
[[nodiscard]] STAmount
|
||||
roundToPosteriorVaultScale(
|
||||
SLE::ConstRef vault,
|
||||
@@ -365,19 +361,19 @@ roundToPosteriorVaultScale(
|
||||
[[nodiscard]] STAmount
|
||||
creditToPosteriorAvailableScale(
|
||||
SLE::ConstRef vault,
|
||||
STAmount const& raw,
|
||||
Number const& raw,
|
||||
Number::RoundingMode roundingMode)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
vault && vault->getType() == ltVAULT,
|
||||
"xrpl::creditToPosteriorAvailableScale : valid Vault sle");
|
||||
XRPL_ASSERT(
|
||||
raw.asset() == vault->at(sfAsset),
|
||||
"xrpl::creditToPosteriorAvailableScale : raw and Vault asset match");
|
||||
if (raw.integral())
|
||||
return raw;
|
||||
|
||||
Asset const asset = vault->at(sfAsset);
|
||||
if (asset.integral())
|
||||
{
|
||||
NumberRoundModeGuard const rg(roundingMode);
|
||||
return STAmount{asset, raw};
|
||||
}
|
||||
|
||||
Number const reference = vault->at(sfAssetsAvailable);
|
||||
int const scale = posteriorScale(vault, reference, raw);
|
||||
return detail::creditToPosteriorScale(asset, reference, scale, raw, roundingMode);
|
||||
@@ -402,6 +398,8 @@ checkOptionalVaultInflow(SLE::ConstRef vault, STAmount const& amount)
|
||||
amount.asset() == vault->at(sfAsset),
|
||||
"xrpl::checkOptionalVaultInflow : amount and Vault asset match");
|
||||
XRPL_ASSERT(!amount.negative(), "xrpl::checkOptionalVaultInflow : non-negative amount");
|
||||
if (amount.negative())
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (getVaultVersion(vault) != VaultVersion::FixedPrecision)
|
||||
return tesSUCCESS;
|
||||
|
||||
|
||||
@@ -859,7 +859,7 @@ ValidVault::finalize(
|
||||
if (afterVault.assetsAvailable != kZero || afterVault.assetsTotal != kZero ||
|
||||
afterVault.lossUnrealized != kZero || updatedShares->sharesTotal != 0 ||
|
||||
(afterVault.version == VaultVersion::FixedPrecision &&
|
||||
afterVault.assetsDeployed != kZero))
|
||||
(afterVault.assetsDeployed != kZero || afterVault.yieldUnrealized != kZero)))
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: created vault must be empty";
|
||||
|
||||
@@ -748,9 +748,9 @@ LoanSet::doApply()
|
||||
view.update(vaultSle);
|
||||
|
||||
// Update the balances in the loan broker
|
||||
// FixedPrecision default subtracts principal from DebtTotal exactly. That
|
||||
// depends on origination never rounding DebtTotal at a scale coarser than
|
||||
// the asset's base scale.
|
||||
// On FixedPrecision Vaults, origination adds the principal to DebtTotal
|
||||
// exactly, and LoanPay and default later subtract exact amounts, so
|
||||
// DebtTotal must never be rounded at a scale coarser than the base scale.
|
||||
adjustBrokerDebtTotal(brokerSle->at(sfDebtTotal), vaultSle, debtTotalDelta, vaultScale);
|
||||
adjustLoanBrokerOwnerCount(view, brokerSle, 1, j_);
|
||||
loanSequenceProxy += 1;
|
||||
|
||||
@@ -214,15 +214,18 @@ class LoanSetFixedPrecision_test : public LoanFixedPrecisionBase
|
||||
// truncates below the full share count, so the clawback succeeds and leaves
|
||||
// shares behind.
|
||||
void
|
||||
testLendingFullClawbackClampsInsteadOfHasObligations()
|
||||
testLendingFullClawbackClampsInsteadOfHasObligations(
|
||||
FeatureBitset const amendments,
|
||||
std::string const& label)
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
testcase(
|
||||
"Lending: full-value VaultClawback with a large AssetsDeployed clamps to "
|
||||
"AssetsAvailable instead of tecHAS_OBLIGATIONS");
|
||||
"AssetsAvailable instead of tecHAS_OBLIGATIONS (" +
|
||||
label + ")");
|
||||
|
||||
Env env(*this, features());
|
||||
Env env(*this, amendments);
|
||||
auto const [issuer, owner, depositor, asset] = setupIou(env, {.clawback = true});
|
||||
|
||||
auto const fixture = setupLendingVault(
|
||||
@@ -599,7 +602,10 @@ public:
|
||||
testLendingAssetsDeployedTwoLoans();
|
||||
testLendingClawbackAndWithdrawWhileLoanOpen();
|
||||
testLendingFinalWithdrawalWhileLoanOpen();
|
||||
testLendingFullClawbackClampsInsteadOfHasObligations();
|
||||
testLendingFullClawbackClampsInsteadOfHasObligations(features(), "all amendments");
|
||||
// FixedPrecision Vaults take the clamped path even without fixCleanup3_1_3.
|
||||
testLendingFullClawbackClampsInsteadOfHasObligations(
|
||||
features() - fixCleanup3_1_3, "without fixCleanup3_1_3");
|
||||
testLendingCashBasisControl();
|
||||
testOriginationGuardBlocksCoarsening();
|
||||
testLoanSetTransferLegShapes();
|
||||
|
||||
@@ -827,12 +827,10 @@ private:
|
||||
|
||||
// Test pre-fixCleanup3_1_3 legacy path: zero-amount clawback
|
||||
// returns early without clamping to assetsAvailable. This needs a
|
||||
// Legacy/CashBasis Vault, not the FixedPrecision Vault the outer
|
||||
// `env` produces, so it runs against its own Env with
|
||||
// featureLendingProtocolV1_2 disabled; the FixedPrecision
|
||||
// equivalent of this scenario is covered above (zero-amount
|
||||
// clawback clamped with outstanding loan) and in
|
||||
// VaultFixedPrecision_test's
|
||||
// Legacy/CashBasis Vault, so it runs against its own Env with
|
||||
// featureLendingProtocolV1_2 disabled. FixedPrecision Vaults always
|
||||
// take the clamped path, with and without fixCleanup3_1_3; see
|
||||
// LoanSetFixedPrecision_test's
|
||||
// testLendingFullClawbackClampsInsteadOfHasObligations.
|
||||
{
|
||||
testcase(
|
||||
|
||||
@@ -80,6 +80,9 @@ private:
|
||||
{
|
||||
vault->at(sfLEVersion) = std::to_underlying(VaultVersion::FixedPrecision);
|
||||
vault->at(sfScale) = *fixedScale;
|
||||
// FixedPrecision derives its total from these two fields.
|
||||
vault->at(sfAssetsAvailable) = assetsTotal;
|
||||
vault->at(sfAssetsDeployed) = Number{0};
|
||||
}
|
||||
return vault;
|
||||
}
|
||||
|
||||
@@ -529,5 +529,53 @@ TEST_F(VaultBalance, credit_to_posterior_scale_ignores_ambient_rounding_mode)
|
||||
EXPECT_EQ(Number(credit), exactDownwardCredit);
|
||||
}
|
||||
|
||||
// creditToPosteriorAvailableScale returns
|
||||
// floor16(AssetsAvailable + raw) - AssetsAvailable, never finer than -Scale.
|
||||
|
||||
TEST_F(VaultBalance, credit_to_posterior_available_scale_integral_passes_through)
|
||||
{
|
||||
auto const vault = makeVault(xrpIssue(), Number{1'000}, VaultVersion::FixedPrecision);
|
||||
STAmount const credit =
|
||||
creditToPosteriorAvailableScale(vault, Number{250}, Number::RoundingMode::Downward);
|
||||
EXPECT_EQ(credit, STAmount(xrpIssue(), 250));
|
||||
}
|
||||
|
||||
TEST_F(VaultBalance, credit_to_posterior_available_scale_floors_to_base_grid)
|
||||
{
|
||||
// Scale 6: the base grid is 1e-6, and AssetsAvailable is small enough
|
||||
// that the posterior grid is the base grid.
|
||||
auto const vault = iouVault(Number{0}, Number{100});
|
||||
STAmount const credit = creditToPosteriorAvailableScale(
|
||||
vault, Number{12'345'678, -7}, Number::RoundingMode::Downward);
|
||||
EXPECT_EQ(credit, iouAmount(Number{1'234'567, -6}));
|
||||
}
|
||||
|
||||
TEST_F(VaultBalance, credit_to_posterior_available_scale_floors_the_sum_across_power_of_ten)
|
||||
{
|
||||
// 9999999999.999999 + 0.000011 crosses 10^10, where the 16-digit grid is
|
||||
// 1e-5. Flooring the delta alone would give 0.00001; flooring the sum
|
||||
// gives 10000000000.00001, so the credit is exactly 0.000011.
|
||||
auto const vault = iouVault(Number{0}, Number{9'999'999'999'999'999, -6});
|
||||
STAmount const credit =
|
||||
creditToPosteriorAvailableScale(vault, Number{11, -6}, Number::RoundingMode::Downward);
|
||||
EXPECT_EQ(credit, iouAmount(Number{11, -6}));
|
||||
EXPECT_EQ(
|
||||
STAmount(iou_, Number{9'999'999'999'999'999, -6} + Number(credit)),
|
||||
iouAmount(Number{1'000'000'000'000'001, -5}));
|
||||
}
|
||||
|
||||
TEST_F(VaultBalance, credit_to_posterior_available_scale_uses_the_exact_raw_amount)
|
||||
{
|
||||
// raw has 17 significant digits. Exact: 0.000009 + 12345678901.234561 =
|
||||
// 12345678901.23457 on the 1e-5 grid, so the credit is
|
||||
// 12345678901.234561, stored as 12345678901.23456. Rounding raw to 16
|
||||
// digits first (12345678901.23456) would floor the sum to
|
||||
// 12345678901.23456 and give a credit one unit lower, 12345678901.23455.
|
||||
auto const vault = iouVault(Number{0}, Number{9, -6});
|
||||
STAmount const credit = creditToPosteriorAvailableScale(
|
||||
vault, Number{12'345'678'901'234'561, -6}, Number::RoundingMode::Downward);
|
||||
EXPECT_EQ(credit, iouAmount(Number{1'234'567'890'123'456, -5}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace xrpl
|
||||
|
||||
Reference in New Issue
Block a user