fix: Fix VaultInvariant and VaultDeposit precision bugs at IOU scale boundaries (#7272)

Co-authored-by: Bart <bthomee@users.noreply.github.com>
This commit is contained in:
Vito Tumas
2026-05-26 18:32:44 +02:00
committed by GitHub
parent 49cb3f45a4
commit 633ef4706f
6 changed files with 820 additions and 181 deletions

View File

@@ -189,7 +189,6 @@ public:
/**
* Checks if this amount evaluates to zero when constrained to a specific
* accounting scale.
*
* For XRP and MPT `roundToScale` is a no-op, returns true only when the amount itself is zero.
* The `scale` argument is ignored in that case.
* For IOU, the amount is rounded to the given scale using Number::RoundingMode::ToNearest mode

View File

@@ -4,9 +4,11 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <optional>
#include <unordered_map>
@@ -79,16 +81,83 @@ private:
std::vector<Shares> beforeMPTs_;
std::unordered_map<uint256, DeltaInfo> deltas_;
/**
* @brief Compute the minimum STAmount scale for rounding invariant
* calculations.
*
* Post-amendment (@c fixCleanup3_2_0) this is simply the posterior
* @c assetsTotal scale. Pre-amendment it is the coarsest scale across
* @p vaultDelta and both asset-field deltas.
*
* @param vaultDelta Delta of the vault's asset balance for this transaction.
* @param rules Active ledger rules (used to check the amendment).
* @returns The minimum scale to apply when rounding vault-related amounts.
*/
[[nodiscard]] std::int32_t
computeVaultMinScale(DeltaInfo const& vaultDelta, Rules const& rules) const;
/**
* @brief Return the vault-asset balance-change delta for an account.
*
* Looks up the ledger-entry delta recorded during @c visitEntry for the
* account entry (XRP), trust line (IOU), or MPToken (MPT) that corresponds
* to the vault asset held by @p id.
*
* @param id Account whose asset delta is requested.
* @returns The delta, or @c std::nullopt if the entry was not touched.
*/
[[nodiscard]] std::optional<DeltaInfo>
deltaAssets(AccountID const& id) const;
/**
* @brief Return the vault-asset delta for the transaction's sending
* account, adjusted for the fee.
*
* Calls @c deltaAssets for @c tx[sfAccount] and, for non-delegated XRP
* transactions, adds the consumed fee back so the invariant sees the net
* asset movement rather than the fee-reduced balance change.
*
* @param tx The transaction being applied.
* @param fee Fee charged by this transaction.
* @returns The fee-adjusted delta, or @c std::nullopt if the net delta is
* zero or the account entry was not touched.
*/
[[nodiscard]] std::optional<DeltaInfo>
deltaAssetsTxAccount(STTx const& tx, XRPAmount fee) const;
/**
* @brief Return the vault-share balance-change delta for an account.
*
* For the vault's pseudo-account the @c MPTokenIssuance outstanding-amount
* delta is returned; for all other accounts the @c MPToken delta is
* returned.
*
* @param id Account whose share delta is requested.
* @returns The delta, or @c std::nullopt if the entry was not touched.
*/
[[nodiscard]] std::optional<DeltaInfo>
deltaShares(AccountID const& id) const;
/**
* @brief Check whether a vault holds no assets.
*
* @param vault Snapshot of the vault to test.
* @returns @c true when both @c assetsAvailable and @c assetsTotal are
* zero.
*/
[[nodiscard]] static bool
isVaultEmpty(Vault const& vault);
public:
// Compute the coarsest scale required to represent all numbers
[[nodiscard]] static std::int32_t
computeCoarsestScale(std::vector<DeltaInfo> const& numbers);
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
// Compute the coarsest scale required to represent all numbers
[[nodiscard]] static std::int32_t
computeCoarsestScale(std::vector<DeltaInfo> const& numbers);
};
} // namespace xrpl