Skip to content

Conversation

@Tapanito
Copy link
Collaborator

@Tapanito Tapanito commented Jan 14, 2026

This PR relaxes Vault Deposit, Withdraw and Clawback invariants for IOUs to allow minimal discrepancy between various balance changes.

High Level Overview of Change

Context of Change

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (non-breaking change that only restructures code)
  • Performance (increase or change in throughput and/or latency)
  • Tests (you added tests for code that already exists, or your new feature included in this PR)
  • Documentation update
  • Chore (no impact to binary, e.g. .gitignore, formatting, dropping support for older tooling)
  • Release

API Impact

  • Public API: New feature (new methods and/or new fields)
  • Public API: Breaking change (in general, breaking changes should only impact the next api_version)
  • libxrpl change (any change that may affect libxrpl or dependents of libxrpl)
  • Peer protocol change (must be backward compatible or bump the peer protocol version)

@Tapanito Tapanito requested a review from ximinez January 14, 2026 19:59
@codecov
Copy link

codecov bot commented Jan 14, 2026

Codecov Report

❌ Patch coverage is 98.36066% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 79.4%. Comparing base (66158d7) to head (f8d441b).

Files with missing lines Patch % Lines
src/xrpld/app/tx/detail/InvariantCheck.cpp 98.4% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##           develop   #6217   +/-   ##
=======================================
  Coverage     79.4%   79.4%           
=======================================
  Files          839     839           
  Lines        71619   71633   +14     
  Branches      8236    8231    -5     
=======================================
+ Hits         56849   56872   +23     
+ Misses       14770   14761    -9     
Files with missing lines Coverage Δ
src/xrpld/app/tx/detail/InvariantCheck.h 100.0% <ø> (ø)
src/xrpld/app/tx/detail/InvariantCheck.cpp 92.3% <98.4%> (+0.1%) ⬆️

... and 4 files with indirect coverage changes

Impacted file tree graph

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Tapanito Tapanito changed the title Relax Vault Invariant to account for rounding errors Add rounding to VaultWithdraw invariants Jan 15, 2026
@Tapanito Tapanito changed the title Add rounding to VaultWithdraw invariants Add rounding to Vault invariants Jan 20, 2026
@Tapanito Tapanito force-pushed the tapanito/lending-vault-invariant branch from a7e80dd to 6b5618e Compare January 20, 2026 12:40
@Tapanito Tapanito force-pushed the tapanito/lending-vault-invariant branch from 6b5618e to 9235ec4 Compare January 20, 2026 16:06
ximinez added a commit that referenced this pull request Jan 21, 2026
- Requires a template for STAmount and Asset.
- Update tests and computeMinScale from #6217 to use scale.
- Convert a few other places to use "scale" correctly.
ximinez added a commit that referenced this pull request Jan 21, 2026
- Requires a template for STAmount and Asset.
- Update tests and computeMinScale from #6217 to use scale.
- Convert a few other places to use "scale" correctly.
Copy link
Collaborator

@ximinez ximinez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created #6246 to make the some of the changes I requested below. Pretty much everything except the changes to the actual ValidVault invariant checks.

Comment on lines 3223 to 3226
auto const vaultDeltaAssets =
roundToAsset(vaultAsset, *maybeVaultDeltaAssets, minScale);

if (vaultDeltaAssets > tx[sfAmount])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is using "round to nearest", it could round vaultDeltaAssets up while tx[sfAmount] doesn't, and yet still be valid. Maybe you should round both?

Comment on lines 3661 to 3662
if (value == beast::zero || asset.integral())
return 0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the Number series values are particularly small, and has one 0, this will throw the rounding off. At an extreme, all the values in the caller could round to 0. I suggest that if value is 0, return value.exponent() (which is the minimum int value) instead. That will guarantee that the result of max_element will represent the largest non-zero exponent.

if (value == beast::zero || asset.integral())
return 0;

auto mantissa = std::abs(value.mantissa());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think taking the absolute value will make any difference. You're only dividing.

Comment on lines 3667 to 3672
// Remove trailing zeros from mantissa, adjusting scale accordingly
while (mantissa % 10 == 0)
{
mantissa /= 10;
++scale;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is right. Let's take an example:

  • 1.00000000 is going to have a mantissa of 1e18 and an exponent of -18.
    • After this loop, mantissa will be 1, and scale will be 0, so it returns 0.
  • Then 1.23456789 will return -8.
  • max_element will return the 0.

Later, roundToAsset, will round both of them to 1. They would look equal when they are definitely not equal.

The canonical way to correctly get the scale is to make an STNumber, and get its exponent.

Comment on lines 3922 to 3926
{
.name = "Mixed scales",
.expectedMinScale = -2,
.values = {Number{1, -2}, Number{5, -3}, Number{3, -2}},
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another case

{
		.name = "Mixed mantissa sizes",
		.expectedMinScale = X,
		.values = {Number{1}, Number{1234, -3}, Number{12345, -6}, Number{123, 1}},
}

@Tapanito Tapanito force-pushed the tapanito/lending-vault-invariant branch from 6f9b206 to c6821ab Compare January 21, 2026 10:42
@Tapanito
Copy link
Collaborator Author

@ximinez apologies for the forced push, I haven't seen your review!

@kennyzlei kennyzlei requested a review from gregtatcam January 21, 2026 17:07
@Tapanito
Copy link
Collaborator Author

@ximinez as we discussed, I rewrote the Invariant to check relative distance between values within some tolerance. For IOU the tolerance is 1 * 10^-13, for MPT/XRP the tolerance is zero.

The IOU tolerance is the smallest tolerance that would cause the unit test to pass. If the tolerance was -14, the unit-test would fail.

@Tapanito Tapanito requested a review from ximinez January 21, 2026 18:03
afterVault.assetsAvailable)
auto const assetsTotalDelta =
afterVault.assetsTotal - beforeVault.assetsTotal;
if (assetsTotalDelta != vaultDeltaAssets)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this first if needed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants