Take baseline changes from original implementation

- Won't build
This commit is contained in:
Ed Hennis
2025-11-16 13:10:01 -05:00
parent 13a12c6402
commit 248d267f21
14 changed files with 158 additions and 17 deletions

View File

@@ -13,6 +13,15 @@ class Number;
std::string
to_string(Number const& amount);
template <typename T>
constexpr bool
isPowerOfTen(T value)
{
while (value >= 10 && value % 10 == 0)
value /= 10;
return value == 1;
}
class Number
{
using rep = std::int64_t;
@@ -21,8 +30,13 @@ class Number
public:
// The range for the mantissa when normalized
constexpr static std::int64_t minMantissa = 1'000'000'000'000'000LL;
constexpr static std::int64_t maxMantissa = 9'999'999'999'999'999LL;
constexpr static rep minMantissa = 1'000'000'000'000'000LL;
static_assert(isPowerOfTen(minMantissa));
constexpr static rep maxMantissa = minMantissa * 10 - 1;
static_assert(maxMantissa == 9'999'999'999'999'999LL);
constexpr static rep maxIntValue = maxMantissa / 100;
static_assert(maxIntValue == 99'999'999'999'999LL);
// The range for the exponent when normalized
constexpr static int minExponent = -32768;

View File

@@ -84,6 +84,12 @@ public:
return holds<Issue>() && get<Issue>().native();
}
bool
integral() const
{
return !holds<Issue>() || get<Issue>().native();
}
friend constexpr bool
operator==(Asset const& lhs, Asset const& rhs);

View File

@@ -155,6 +155,9 @@ public:
int
exponent() const noexcept;
bool
integral() const noexcept;
bool
native() const noexcept;
@@ -435,6 +438,12 @@ STAmount::exponent() const noexcept
return mOffset;
}
inline bool
STAmount::integral() const noexcept
{
return mAsset.integral();
}
inline bool
STAmount::native() const noexcept
{
@@ -553,7 +562,7 @@ STAmount::clear()
{
// The -100 is used to allow 0 to sort less than a small positive values
// which have a negative exponent.
mOffset = native() ? 0 : -100;
mOffset = integral() ? 0 : -100;
mValue = 0;
mIsNegative = false;
}

View File

@@ -482,6 +482,8 @@ public:
value_type
operator*() const;
/// Do not use operator->() unless the field is required, or you've checked
/// that it's set.
T const*
operator->() const;
@@ -718,6 +720,8 @@ STObject::Proxy<T>::operator*() const -> value_type
return this->value();
}
/// Do not use operator->() unless the field is required, or you've checked that
/// it's set.
template <class T>
T const*
STObject::Proxy<T>::operator->() const

View File

@@ -23,6 +23,7 @@ systemName()
/** Number of drops in the genesis account. */
constexpr XRPAmount INITIAL_XRP{100'000'000'000 * DROPS_PER_XRP};
static_assert(INITIAL_XRP.drops() == 100'000'000'000'000'000);
/** Returns true if the amount does not exceed the initial XRP in existence. */
inline bool

View File

@@ -479,10 +479,10 @@ LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
{sfAccount, soeREQUIRED},
{sfData, soeOPTIONAL},
{sfAsset, soeREQUIRED},
{sfAssetsTotal, soeREQUIRED},
{sfAssetsAvailable, soeREQUIRED},
{sfAssetsTotal, soeDEFAULT},
{sfAssetsAvailable, soeDEFAULT},
{sfAssetsMaximum, soeDEFAULT},
{sfLossUnrealized, soeREQUIRED},
{sfLossUnrealized, soeDEFAULT},
{sfShareMPTID, soeREQUIRED},
{sfWithdrawalPolicy, soeREQUIRED},
{sfScale, soeDEFAULT},

View File

@@ -1384,7 +1384,7 @@ private:
// equal asset deposit: unit test to exercise the rounding-down of
// LPTokens in the AMMHelpers.cpp: adjustLPTokens calculations
// The LPTokens need to have 16 significant digits and a fractional part
for (Number const deltaLPTokens :
for (Number const& deltaLPTokens :
{Number{UINT64_C(100000'0000000009), -10},
Number{UINT64_C(100000'0000000001), -10}})
{

View File

@@ -3684,7 +3684,32 @@ class Vault_test : public beast::unit_test::suite
});
testCase(18, [&, this](Env& env, Data d) {
testcase("Scale deposit overflow on second deposit");
testcase("MPT scale deposit overflow");
// The computed number of shares can not be represented as an MPT
// without truncation
{
auto tx = d.vault.deposit(
{.depositor = d.depositor,
.id = d.keylet.key,
.amount = d.asset(5)});
env(tx, ter{tecPRECISION_LOSS});
env.close();
}
});
testCase(13, [&, this](Env& env, Data d) {
testcase("MPT scale deposit overflow on first deposit");
auto tx = d.vault.deposit(
{.depositor = d.depositor,
.id = d.keylet.key,
.amount = d.asset(10)});
env(tx, ter{tecPRECISION_LOSS});
env.close();
});
testCase(13, [&, this](Env& env, Data d) {
testcase("MPT scale deposit overflow on second deposit");
{
auto tx = d.vault.deposit(
@@ -3705,8 +3730,8 @@ class Vault_test : public beast::unit_test::suite
}
});
testCase(18, [&, this](Env& env, Data d) {
testcase("Scale deposit overflow on total shares");
testCase(13, [&, this](Env& env, Data d) {
testcase("No MPT scale deposit overflow on total shares");
{
auto tx = d.vault.deposit(
@@ -3722,7 +3747,7 @@ class Vault_test : public beast::unit_test::suite
{.depositor = d.depositor,
.id = d.keylet.key,
.amount = d.asset(5)});
env(tx, ter{tecPATH_DRY});
env(tx);
env.close();
}
});
@@ -4006,6 +4031,28 @@ class Vault_test : public beast::unit_test::suite
testCase(18, [&, this](Env& env, Data d) {
testcase("Scale withdraw overflow");
{
auto tx = d.vault.deposit(
{.depositor = d.depositor,
.id = d.keylet.key,
.amount = d.asset(5)});
env(tx, ter{tecPRECISION_LOSS});
env.close();
}
{
auto tx = d.vault.withdraw(
{.depositor = d.depositor,
.id = d.keylet.key,
.amount = STAmount(d.asset, Number(10, 0))});
env(tx, ter{tecPRECISION_LOSS});
env.close();
}
});
testCase(13, [&, this](Env& env, Data d) {
testcase("MPT scale withdraw overflow");
{
auto tx = d.vault.deposit(
{.depositor = d.depositor,
@@ -4224,6 +4271,29 @@ class Vault_test : public beast::unit_test::suite
testCase(18, [&, this](Env& env, Data d) {
testcase("Scale clawback overflow");
{
auto tx = d.vault.deposit(
{.depositor = d.depositor,
.id = d.keylet.key,
.amount = d.asset(5)});
env(tx, ter(tecPRECISION_LOSS));
env.close();
}
{
auto tx = d.vault.clawback(
{.issuer = d.issuer,
.id = d.keylet.key,
.holder = d.depositor,
.amount = STAmount(d.asset, Number(10, 0))});
env(tx, ter{tecPRECISION_LOSS});
env.close();
}
});
testCase(13, [&, this](Env& env, Data d) {
testcase("MPT Scale clawback overflow");
{
auto tx = d.vault.deposit(
{.depositor = d.depositor,
@@ -4525,7 +4595,8 @@ class Vault_test : public beast::unit_test::suite
BEAST_EXPECT(checkString(vault, sfAssetsAvailable, "50"));
BEAST_EXPECT(checkString(vault, sfAssetsMaximum, "1000"));
BEAST_EXPECT(checkString(vault, sfAssetsTotal, "50"));
BEAST_EXPECT(checkString(vault, sfLossUnrealized, "0"));
// Since this field is default, it is not returned.
BEAST_EXPECT(!vault.isMember(sfLossUnrealized.getJsonName()));
auto const strShareID = strHex(sle->at(sfShareMPTID));
BEAST_EXPECT(checkString(vault, sfShareMPTID, strShareID));

View File

@@ -2413,6 +2413,19 @@ ValidVault::finalize(
beforeVault_.empty() || beforeVault_[0].key == afterVault.key,
"ripple::ValidVault::finalize : single vault operation");
if (!afterVault.assetsTotal.representable() ||
!afterVault.assetsAvailable.representable() ||
!afterVault.assetsMaximum.representable() ||
!afterVault.lossUnrealized.representable())
{
JLOG(j.fatal()) << "Invariant failed: vault overflowed maximum current "
"representable integer value";
XRPL_ASSERT(
enforce,
"ripple::ValidVault::finalize : vault integer limit invariant");
return !enforce; // That's all we can do here
}
auto const updatedShares = [&]() -> std::optional<Shares> {
// At this moment we only know that a vault is being updated and there
// might be some MPTokenIssuance objects which are also updated in the

View File

@@ -71,9 +71,13 @@ VaultClawback::preclaim(PreclaimContext const& ctx)
}
Asset const vaultAsset = vault->at(sfAsset);
if (auto const amount = ctx.tx[~sfAmount];
amount && vaultAsset != amount->asset())
if (auto const amount = ctx.tx[~sfAmount])
{
if (vaultAsset != amount->asset())
return tecWRONG_ASSET;
else if (!amount->validNumber())
return tecPRECISION_LOSS;
}
if (vaultAsset.native())
{

View File

@@ -204,6 +204,13 @@ VaultCreate::doApply()
vault->at(sfWithdrawalPolicy) = vaultStrategyFirstComeFirstServe;
if (scale)
vault->at(sfScale) = scale;
if (asset.integral())
{
// Only the Maximum can be a non-zero value, so only it needs to be
// checked.
if (!vault->at(sfAssetsMaximum).value().valid(Number::compatible))
return tecLIMIT_EXCEEDED;
}
view().insert(vault);
// Explicitly create MPToken for the vault owner

View File

@@ -42,6 +42,9 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
if (assets.asset() != vaultAsset)
return tecWRONG_ASSET;
if (!assets.validNumber())
return tecPRECISION_LOSS;
if (vaultAsset.native())
; // No special checks for XRP
else if (vaultAsset.holds<MPTIssue>())
@@ -227,14 +230,14 @@ VaultDeposit::doApply()
return tecINTERNAL; // LCOV_EXCL_LINE
sharesCreated = *maybeShares;
}
if (sharesCreated == beast::zero)
if (sharesCreated == beast::zero || !sharesCreated.validNumber())
return tecPRECISION_LOSS;
auto const maybeAssets =
sharesToAssetsDeposit(vault, sleIssuance, sharesCreated);
if (!maybeAssets)
return tecINTERNAL; // LCOV_EXCL_LINE
else if (*maybeAssets > amount)
else if (*maybeAssets > amount || !maybeAssets->validNumber())
{
// LCOV_EXCL_START
JLOG(j_.error()) << "VaultDeposit: would take more than offered.";

View File

@@ -144,6 +144,11 @@ VaultSet::doApply()
tx[sfAssetsMaximum] < *vault->at(sfAssetsTotal))
return tecLIMIT_EXCEEDED;
vault->at(sfAssetsMaximum) = tx[sfAssetsMaximum];
if (vault->at(sfAsset).value().integral())
{
if (!vault->at(sfAssetsMaximum).value().valid(Number::compatible))
return tecLIMIT_EXCEEDED;
}
}
if (auto const domainId = tx[~sfDomainID]; domainId)

View File

@@ -174,6 +174,8 @@ VaultWithdraw::doApply()
if (!maybeAssets)
return tecINTERNAL; // LCOV_EXCL_LINE
assetsWithdrawn = *maybeAssets;
if (!assetsWithdrawn.validNumber())
return tecPRECISION_LOSS;
}
else if (amount.asset() == share)
{
@@ -184,6 +186,8 @@ VaultWithdraw::doApply()
if (!maybeAssets)
return tecINTERNAL; // LCOV_EXCL_LINE
assetsWithdrawn = *maybeAssets;
if (!assetsWithdrawn.validNumber())
return tecPRECISION_LOSS;
}
else
return tefINTERNAL; // LCOV_EXCL_LINE