fix: Fix IOU precision issues in LoanBrokerCover transactions (#7274)

This commit is contained in:
Vito Tumas
2026-05-21 16:51:58 +02:00
committed by GitHub
parent 795dc5e364
commit 7fdaa0a5ef
10 changed files with 546 additions and 11 deletions

View File

@@ -1205,6 +1205,100 @@ public:
//--------------------------------------------------------------------------
void
testIsZeroAtScale()
{
testcase("isZeroAtScale");
Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)};
// IOU: 10 IOU — mantissa = kMinValue (10^15), exponent = -14.
// One ULP at this scale is 10^-14; half-ULP is 5*10^-15.
{
STAmount const ref{usd, STAmount::kMinValue, -14};
int const refScale = ref.exponent(); // -14
BEAST_EXPECT(refScale == -14);
// Zero rounds to zero at any scale.
STAmount const iouZero{usd, 0};
BEAST_EXPECT(iouZero.isZeroAtScale(refScale));
// Sub-ULP: 1e-16 IOU (mantissa = kMinValue, exponent = -31).
// Far below half-ULP → rounds to zero.
STAmount const subUlp{usd, STAmount::kMinValue, -31};
BEAST_EXPECT(subUlp.isZeroAtScale(refScale));
// One ULP: 1e-14 IOU (mantissa = kMinValue, exponent = -29).
// Exactly the smallest representable unit at refScale → not zero.
STAmount const oneUlp{usd, STAmount::kMinValue, -29};
BEAST_EXPECT(!oneUlp.isZeroAtScale(refScale));
// The reference value itself: exponent == scale → returned
// unchanged → not zero.
BEAST_EXPECT(!ref.isZeroAtScale(refScale));
// A much larger value: certainly not zero at this scale.
STAmount const large{usd, STAmount::kMinValue, 0}; // 1e15 IOU
BEAST_EXPECT(!large.isZeroAtScale(refScale));
// When scale equals the value's own exponent, roundToScale
// short-circuits and returns the value unchanged.
BEAST_EXPECT(!subUlp.isZeroAtScale(subUlp.exponent()));
BEAST_EXPECT(!oneUlp.isZeroAtScale(oneUlp.exponent()));
// Half-ULP boundary. roundToScale forms (value + ref) - ref
// where ref = 10 IOU has mantissa 1e15 (LSB 0, even).
// Number's default rounding is to-nearest-even, so an exact
// half-ULP tie rounds toward the even-LSB neighbour — the
// reference itself — and the round-trip result is zero.
// Just below half-ULP rounds the same way; just above
// clears half-ULP and bumps the mantissa to 1e15 + 1.
STAmount const justBelowHalf{usd, STAmount::kMinValue * 4, -30};
BEAST_EXPECT(justBelowHalf.isZeroAtScale(refScale));
STAmount const halfUlp{usd, STAmount::kMinValue * 5, -30};
BEAST_EXPECT(halfUlp.isZeroAtScale(refScale));
STAmount const justAboveHalf{usd, STAmount::kMinValue * 6, -30};
BEAST_EXPECT(!justAboveHalf.isZeroAtScale(refScale));
// Large magnitude gap: dust value far below an enormous scale.
// 1e-80 with scale +15 — the value vanishes utterly.
STAmount const dust{usd, STAmount::kMinValue, -95};
BEAST_EXPECT(dust.isZeroAtScale(15));
// Negative values mirror positive behaviour.
STAmount const negSubUlp{usd, STAmount::kMinValue, -31, true};
BEAST_EXPECT(negSubUlp.isZeroAtScale(refScale));
STAmount const negOneUlp{usd, STAmount::kMinValue, -29, true};
BEAST_EXPECT(!negOneUlp.isZeroAtScale(refScale));
}
// XRP is integral — roundToScale short-circuits, value is preserved.
{
STAmount const xrp{XRPAmount{1}};
BEAST_EXPECT(!xrp.isZeroAtScale(-14));
BEAST_EXPECT(!xrp.isZeroAtScale(0));
STAmount const xrpZero{XRPAmount{0}};
BEAST_EXPECT(xrpZero.isZeroAtScale(-14));
}
// MPT is integral — same short-circuit behaviour as XRP.
{
MPTIssue const mpt{makeMptID(1, AccountID(0x4985601))};
STAmount const mptAmt{mpt, 1};
BEAST_EXPECT(!mptAmt.isZeroAtScale(0));
BEAST_EXPECT(!mptAmt.isZeroAtScale(-14));
STAmount const mptZero{mpt, 0};
BEAST_EXPECT(mptZero.isZeroAtScale(0));
}
}
//--------------------------------------------------------------------------
void
run() override
{
@@ -1223,6 +1317,7 @@ public:
testCanSubtractXRP();
testCanSubtractIOU();
testCanSubtractMPT();
testIsZeroAtScale();
}
};