mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-05 17:56:49 +00:00
Compare commits
13 Commits
a1q123456/
...
pratik/cov
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11560fb51b | ||
|
|
4e907d9662 | ||
|
|
5f6bb93b5c | ||
|
|
2b4d8ef3a2 | ||
|
|
13358f5042 | ||
|
|
e1946278f9 | ||
|
|
c4115ee4be | ||
|
|
e2ba7a728b | ||
|
|
a5db99fb96 | ||
|
|
20678e6bc4 | ||
|
|
42eadf0416 | ||
|
|
c327fc1ee2 | ||
|
|
1297f0bc52 |
@@ -1466,7 +1466,10 @@ admin = 127.0.0.1
|
||||
protocol = http
|
||||
|
||||
[port_peer]
|
||||
port = 2459
|
||||
# Many servers still use the legacy port of 51235, so for backward-compatibility
|
||||
# we maintain that port number here. However, for new servers we recommend
|
||||
# changing this to the default port of 2459.
|
||||
port = 51235
|
||||
ip = 0.0.0.0
|
||||
# alternatively, to accept connections on IPv4 + IPv6, use:
|
||||
#ip = ::
|
||||
|
||||
@@ -2,122 +2,12 @@
|
||||
#
|
||||
# These packages are required to run the code generation scripts that
|
||||
# parse macro files and generate C++ wrapper classes.
|
||||
#
|
||||
# To update a package to the latest version (hashin will rewrite the
|
||||
# entry in this file with the new version and hashes):
|
||||
# pip install hashin
|
||||
# hashin <package> cmake/scripts/codegen/requirements.txt
|
||||
# Or to pin a specific version:
|
||||
# hashin <package>==<version> cmake/scripts/codegen/requirements.txt
|
||||
#
|
||||
# Then install the updated packages:
|
||||
# pip install --require-hashes -r cmake/scripts/codegen/requirements.txt
|
||||
#
|
||||
# If pip install fails because a transitive dependency is missing hashes,
|
||||
# use hashin to pin that dependency with hashes here as well:
|
||||
# hashin <dependency> cmake/scripts/codegen/requirements.txt
|
||||
|
||||
# C preprocessor for Python - used to preprocess macro files
|
||||
pcpp==1.30 \
|
||||
--hash=sha256:05fe08292b6da57f385001c891a87f40d6aa7f46787b03e8ba326d20a3297c6e \
|
||||
--hash=sha256:5af9fbce55f136d7931ae915fae03c34030a3b36c496e72d9636cedc8e2543a1
|
||||
pcpp>=1.30
|
||||
|
||||
# Parser combinator library - used to parse the macro DSL
|
||||
pyparsing==3.3.2 \
|
||||
--hash=sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d \
|
||||
--hash=sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc
|
||||
pyparsing>=3.0.0
|
||||
|
||||
# Template engine - used to generate C++ code from templates
|
||||
Mako==1.3.12 \
|
||||
--hash=sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9 \
|
||||
--hash=sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a
|
||||
MarkupSafe==3.0.3 \
|
||||
--hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \
|
||||
--hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \
|
||||
--hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \
|
||||
--hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \
|
||||
--hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \
|
||||
--hash=sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c \
|
||||
--hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \
|
||||
--hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \
|
||||
--hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \
|
||||
--hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \
|
||||
--hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \
|
||||
--hash=sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26 \
|
||||
--hash=sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1 \
|
||||
--hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \
|
||||
--hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \
|
||||
--hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \
|
||||
--hash=sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695 \
|
||||
--hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \
|
||||
--hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \
|
||||
--hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \
|
||||
--hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \
|
||||
--hash=sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa \
|
||||
--hash=sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559 \
|
||||
--hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \
|
||||
--hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \
|
||||
--hash=sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758 \
|
||||
--hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \
|
||||
--hash=sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8 \
|
||||
--hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \
|
||||
--hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \
|
||||
--hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \
|
||||
--hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \
|
||||
--hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \
|
||||
--hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \
|
||||
--hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \
|
||||
--hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \
|
||||
--hash=sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2 \
|
||||
--hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \
|
||||
--hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \
|
||||
--hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \
|
||||
--hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \
|
||||
--hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \
|
||||
--hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \
|
||||
--hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \
|
||||
--hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \
|
||||
--hash=sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e \
|
||||
--hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \
|
||||
--hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \
|
||||
--hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \
|
||||
--hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \
|
||||
--hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \
|
||||
--hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \
|
||||
--hash=sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b \
|
||||
--hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \
|
||||
--hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \
|
||||
--hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \
|
||||
--hash=sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d \
|
||||
--hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \
|
||||
--hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \
|
||||
--hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \
|
||||
--hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \
|
||||
--hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \
|
||||
--hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \
|
||||
--hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \
|
||||
--hash=sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c \
|
||||
--hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \
|
||||
--hash=sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8 \
|
||||
--hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \
|
||||
--hash=sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6 \
|
||||
--hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \
|
||||
--hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \
|
||||
--hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \
|
||||
--hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \
|
||||
--hash=sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7 \
|
||||
--hash=sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419 \
|
||||
--hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \
|
||||
--hash=sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1 \
|
||||
--hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \
|
||||
--hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \
|
||||
--hash=sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42 \
|
||||
--hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \
|
||||
--hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \
|
||||
--hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \
|
||||
--hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \
|
||||
--hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \
|
||||
--hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591 \
|
||||
--hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \
|
||||
--hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \
|
||||
--hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50
|
||||
Mako>=1.2.2
|
||||
|
||||
@@ -4,8 +4,39 @@
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/st.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Broker cover preclaim precision guard (fixCleanup3_2_0).
|
||||
*
|
||||
* Prevents a "silent sub-ULP no-op" where a deposit, withdrawal, or clawback
|
||||
* amount is so small that it rounds to zero at `sfCoverAvailable`'s scale.
|
||||
* Without this guard, both the pseudo trust-line and `sfCoverAvailable` would
|
||||
* identically absorb the rounded zero, resulting in a successful transaction
|
||||
* (tesSUCCESS) where no funds actually moved.
|
||||
*
|
||||
* @param view Apply view (rules used for amendment gating).
|
||||
* @param sleBroker The loan broker SLE (read-only).
|
||||
* @param vaultAsset The underlying vault asset (the broker's cover asset).
|
||||
* @param amount The effective subtraction/addition amount.
|
||||
* @param j Journal for logging.
|
||||
* @param logPrefix Transactor name for log diagnostics.
|
||||
*
|
||||
* @return `tecPRECISION_LOSS` if the request rounds to zero at cover scale.
|
||||
* `tesSUCCESS` if the amendment is disabled, the amount is true zero,
|
||||
* or the request is safely supra-ULP.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canApplyToBrokerCover(
|
||||
ReadView const& view,
|
||||
SLE::const_ref sleBroker,
|
||||
Asset const& vaultAsset,
|
||||
STAmount const& amount,
|
||||
beast::Journal j,
|
||||
std::string_view logPrefix);
|
||||
|
||||
// Lending protocol has dependencies, so capture them here.
|
||||
bool
|
||||
checkLendingProtocolDependencies(Rules const& rules, STTx const& tx);
|
||||
|
||||
@@ -184,6 +184,24 @@ public:
|
||||
[[nodiscard]] STAmount const&
|
||||
value() const noexcept;
|
||||
|
||||
/**
|
||||
* 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 the current rounding mode)
|
||||
* and the result is checked for zero; if `scale <= exponent()`, `roundToScale` short-circuits
|
||||
* and returns the value unchanged, so this returns false for any non-zero amount.
|
||||
*
|
||||
* @param scale The target accounting scale to evaluate against.
|
||||
* @return `true` if this amount rounds to zero at the given scale, `false` otherwise.
|
||||
*
|
||||
* @see roundToScale
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isZeroAtScale(int scale) const;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Operators
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -24,10 +25,41 @@
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
[[nodiscard]] TER
|
||||
canApplyToBrokerCover(
|
||||
ReadView const& view,
|
||||
SLE::const_ref sleBroker,
|
||||
Asset const& vaultAsset,
|
||||
STAmount const& amount,
|
||||
beast::Journal j,
|
||||
std::string_view logPrefix)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
sleBroker && sleBroker->getType() == ltLOAN_BROKER,
|
||||
"xrpl::canApplyToBrokerCover : valid LoanBroker sle");
|
||||
XRPL_ASSERT(
|
||||
vaultAsset == amount.asset() && amount > beast::kZero,
|
||||
"xrpl::canApplyToBrokerCover : valid amount for asset");
|
||||
|
||||
if (!view.rules().enabled(fixCleanup3_2_0))
|
||||
return tesSUCCESS;
|
||||
|
||||
int const coverScale = scale(sleBroker->at(sfCoverAvailable), vaultAsset);
|
||||
if (amount.isZeroAtScale(coverScale))
|
||||
{
|
||||
JLOG(j.warn()) << logPrefix << ": amount " << amount.getFullText()
|
||||
<< " rounds to zero at cover scale " << coverScale;
|
||||
return tecPRECISION_LOSS;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
bool
|
||||
checkLendingProtocolDependencies(Rules const& rules, STTx const& tx)
|
||||
{
|
||||
|
||||
@@ -1738,4 +1738,9 @@ divRoundStrict(STAmount const& num, STAmount const& den, Asset const& asset, boo
|
||||
return divRoundImpl<NumberRoundModeGuard>(num, den, asset, roundUp);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
STAmount::isZeroAtScale(int scale) const
|
||||
{
|
||||
return roundToScale(*this, scale).signum() == 0;
|
||||
}
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -184,7 +184,7 @@ EscrowCancel::doApply()
|
||||
return escrowUnlockApplyHelper<T>(
|
||||
ctx_.view(),
|
||||
kParityRate,
|
||||
ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : slep,
|
||||
slep,
|
||||
preFeeBalance_,
|
||||
amount,
|
||||
issuer,
|
||||
|
||||
@@ -291,6 +291,10 @@ LoanBrokerCoverClawback::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
STAmount const& clawAmount = *findClawAmount;
|
||||
|
||||
if (auto const ret = canApplyToBrokerCover(
|
||||
ctx.view, sleBroker, vaultAsset, clawAmount, ctx.j, "LoanBrokerCoverClawback"))
|
||||
return ret;
|
||||
|
||||
// Explicitly check the balance of the trust line / MPT to make sure the
|
||||
// balance is actually there. It should always match `sfCoverAvailable`, so
|
||||
// if there isn't, this is an internal error.
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#include <xrpl/tx/transactors/lending/LoanBrokerCoverDeposit.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/helpers/LendingHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
@@ -106,8 +108,6 @@ LoanBrokerCoverDeposit::doApply()
|
||||
auto const& tx = ctx_.tx;
|
||||
|
||||
auto const brokerID = tx[sfLoanBrokerID];
|
||||
auto const amount = tx[sfAmount];
|
||||
|
||||
auto broker = view().peek(keylet::loanbroker(brokerID));
|
||||
if (!broker)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
@@ -117,9 +117,25 @@ LoanBrokerCoverDeposit::doApply()
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const vaultAsset = vault->at(sfAsset);
|
||||
|
||||
auto const brokerPseudoID = broker->at(sfAccount);
|
||||
|
||||
bool const fix320Enabled = view().rules().enabled(fixCleanup3_2_0);
|
||||
auto const amount = [&]() -> STAmount {
|
||||
if (!fix320Enabled)
|
||||
return tx[sfAmount];
|
||||
|
||||
return roundToScale(
|
||||
tx[sfAmount],
|
||||
scale(broker->at(sfCoverAvailable), vaultAsset),
|
||||
Number::RoundingMode::Downward);
|
||||
}();
|
||||
|
||||
if (fix320Enabled && amount == beast::kZero)
|
||||
{
|
||||
JLOG(ctx_.journal.warn()) << "LoanBrokerCoverDeposit: deposit amount: " << tx[sfAmount]
|
||||
<< " is zero at loan broker scale";
|
||||
return tecPRECISION_LOSS;
|
||||
}
|
||||
// Transfer assets from depositor to pseudo-account.
|
||||
if (auto ter =
|
||||
accountSend(view(), accountID_, brokerPseudoID, amount, j_, WaiveTransferFee::Yes))
|
||||
|
||||
@@ -93,6 +93,11 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx)
|
||||
if (amount.asset() != vaultAsset)
|
||||
return tecWRONG_ASSET;
|
||||
|
||||
// Helper handles both IOU and MPT correctly without explicit branching.
|
||||
if (auto const ret = canApplyToBrokerCover(
|
||||
ctx.view, sleBroker, vaultAsset, amount, ctx.j, "LoanBrokerCoverWithdraw"))
|
||||
return ret;
|
||||
|
||||
// The broker's pseudo-account is the source of funds.
|
||||
auto const pseudoAccountID = sleBroker->at(sfAccount);
|
||||
// Cannot transfer a non-transferable Asset
|
||||
|
||||
@@ -886,70 +886,6 @@ struct EscrowToken_test : public beast::unit_test::Suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testIOUCancelDoApply(FeatureBitset features)
|
||||
{
|
||||
testcase("IOU Cancel DoApply");
|
||||
using namespace jtx;
|
||||
using namespace std::literals;
|
||||
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const gw = Account("gw");
|
||||
auto const usd = gw["USD"];
|
||||
|
||||
env.fund(XRP(10'000), alice, bob, gw);
|
||||
env.close();
|
||||
|
||||
env(fset(gw, asfAllowTrustLineLocking));
|
||||
env.close();
|
||||
|
||||
env.trust(usd(100'000), alice);
|
||||
env.trust(usd(100'000), bob);
|
||||
env.close();
|
||||
|
||||
env(pay(gw, alice, usd(10'000)));
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow::create(alice, bob, usd(1'000)),
|
||||
escrow::kFinishTime(env.now() + 1s),
|
||||
escrow::kCancelTime(env.now() + 2s),
|
||||
Fee(baseFee));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(alice, usd) == usd(9'000));
|
||||
|
||||
env(pay(alice, gw, usd(9'000)));
|
||||
env.close();
|
||||
|
||||
env(trust(alice, usd(0)));
|
||||
env.close();
|
||||
|
||||
auto const trustLineKey = keylet::line(alice.id(), gw.id(), usd.currency);
|
||||
BEAST_EXPECT(!env.current()->exists(trustLineKey));
|
||||
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
auto const expectedResult = env.current()->rules().enabled(fixCleanup3_2_0)
|
||||
? Ter(tesSUCCESS)
|
||||
: Ter(tefEXCEPTION);
|
||||
env(escrow::cancel(alice, alice, seq), Fee(baseFee), expectedResult);
|
||||
env.close();
|
||||
|
||||
if (env.current()->rules().enabled(fixCleanup3_2_0))
|
||||
{
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq)));
|
||||
BEAST_EXPECT(env.current()->exists(trustLineKey));
|
||||
BEAST_EXPECT(env.balance(alice, usd) == usd(1'000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testIOUBalances(FeatureBitset features)
|
||||
{
|
||||
@@ -3951,7 +3887,6 @@ struct EscrowToken_test : public beast::unit_test::Suite
|
||||
testIOUFinishPreclaim(features);
|
||||
testIOUFinishDoApply(features);
|
||||
testIOUCancelPreclaim(features);
|
||||
testIOUCancelDoApply(features);
|
||||
testIOUBalances(features);
|
||||
testIOUMetaAndOwnership(features);
|
||||
testIOURippleState(features);
|
||||
@@ -3993,7 +3928,6 @@ public:
|
||||
{all - featureSingleAssetVault - featureLendingProtocol, all})
|
||||
{
|
||||
testIOUWithFeats(feats);
|
||||
testIOUWithFeats(feats - fixCleanup3_2_0);
|
||||
testMPTWithFeats(feats);
|
||||
testMPTWithFeats(feats - fixTokenEscrowV1);
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
@@ -1823,6 +1824,287 @@ class LoanBroker_test : public beast::unit_test::Suite
|
||||
testRIPD4274MPT();
|
||||
}
|
||||
|
||||
// Exercises canApplyToBrokerCover (fixCleanup3_2_0): a deposit, withdraw,
|
||||
// or clawback whose amount rounds to zero at sfCoverAvailable's precision
|
||||
// scale must be rejected with tecPRECISION_LOSS once the amendment is on,
|
||||
// and must silently succeed without changing sfCoverAvailable when off.
|
||||
void
|
||||
testCoverPrecisionGuard()
|
||||
{
|
||||
using namespace jtx;
|
||||
using namespace loanBroker;
|
||||
|
||||
Account const issuer{"issuer"};
|
||||
Account const alice{"alice"};
|
||||
|
||||
// sfCoverAvailable = 10 IOU → STAmount exponent = -14.
|
||||
// Anything < 5e-15 rounds to zero at that scale.
|
||||
// 1e-16 is the representative sub-ULP probe amount.
|
||||
|
||||
// Shared setup: funds accounts, creates a vault + broker with 10 IOU
|
||||
// cover, and returns {brokerKeylet, iou}.
|
||||
auto const setup = [&](Env& env) -> std::pair<Keylet, PrettyAsset> {
|
||||
Vault const vault{env};
|
||||
|
||||
env.fund(XRP(100'000), issuer, alice);
|
||||
env.close();
|
||||
env(fset(issuer, asfAllowTrustLineClawback));
|
||||
env.close();
|
||||
|
||||
PrettyAsset const iou = issuer["IOU"];
|
||||
env(trust(alice, iou(1'000'000)));
|
||||
env.close();
|
||||
env(pay(issuer, alice, iou(1'000)));
|
||||
env.close();
|
||||
|
||||
auto [createTx, vaultKeylet] = vault.create({.owner = alice, .asset = iou});
|
||||
env(createTx);
|
||||
env.close();
|
||||
|
||||
auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice));
|
||||
env(set(alice, vaultKeylet.key));
|
||||
env.close();
|
||||
|
||||
env(coverDeposit(alice, brokerKeylet.key, iou(10)));
|
||||
env.close();
|
||||
|
||||
return {brokerKeylet, iou};
|
||||
};
|
||||
|
||||
auto runTestCases = [&](FeatureBitset features) {
|
||||
TER const expected =
|
||||
features[fixCleanup3_2_0] ? TER{tecPRECISION_LOSS} : TER{tesSUCCESS};
|
||||
|
||||
{
|
||||
testcase("Cover precision guard: Deposit zero-at-scale");
|
||||
Env env{*this, features};
|
||||
auto const [brokerKeylet, iou] = setup(env);
|
||||
PrettyAmount const subUlpAmt = iou(Number{1, -16});
|
||||
auto const coverBefore = env.le(brokerKeylet)->at(sfCoverAvailable);
|
||||
env(coverDeposit(alice, brokerKeylet.key, subUlpAmt), Ter(expected));
|
||||
env.close();
|
||||
if (expected == tesSUCCESS)
|
||||
{
|
||||
if (auto const broker = env.le(brokerKeylet); BEAST_EXPECT(broker))
|
||||
BEAST_EXPECT(broker->at(sfCoverAvailable) == coverBefore);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Cover precision guard: Deposit rounds down");
|
||||
// Both cases succeed; post-fix the amount is rounded DOWN to
|
||||
// cover scale first, so the delta differs from pre-fix
|
||||
// Input: 1.8e-14 IOU (sub-scale at cover scale -14)
|
||||
// Pre-fix: 10 + 1.8e-14 → round-to-nearest →
|
||||
// 10.00000000000002 → delta 2e-14
|
||||
// Post-fix: roundToScale(1.8e-14, -14, Downward) = 1e-14;
|
||||
// 10 + 1e-14 = 10.00000000000001 → delta 1e-14
|
||||
Env env{*this, features};
|
||||
auto const [brokerKeylet, iou] = setup(env);
|
||||
PrettyAmount const subUlpAmt = iou(Number{18, -15});
|
||||
auto const coverBefore = env.le(brokerKeylet)->at(sfCoverAvailable);
|
||||
env(coverDeposit(alice, brokerKeylet.key, subUlpAmt), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
auto const brokerAfter = env.le(brokerKeylet);
|
||||
if (!BEAST_EXPECT(brokerAfter))
|
||||
return;
|
||||
|
||||
Number const delta = features[fixCleanup3_2_0] ? Number{1, -14} : Number{2, -14};
|
||||
BEAST_EXPECT(brokerAfter->at(sfCoverAvailable) - coverBefore == delta);
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Cover precision guard: Withdraw");
|
||||
Env env{*this, features};
|
||||
auto const [brokerKeylet, iou] = setup(env);
|
||||
PrettyAmount const subUlpAmt = iou(Number{1, -16});
|
||||
auto const coverBefore = env.le(brokerKeylet)->at(sfCoverAvailable);
|
||||
auto const aliceBalanceBefore = env.balance(alice, iou);
|
||||
env(coverWithdraw(alice, brokerKeylet.key, subUlpAmt), Ter(expected));
|
||||
env.close();
|
||||
if (expected == tesSUCCESS)
|
||||
{
|
||||
if (auto const broker = env.le(brokerKeylet); BEAST_EXPECT(broker))
|
||||
BEAST_EXPECT(broker->at(sfCoverAvailable) == coverBefore);
|
||||
BEAST_EXPECT(env.balance(alice, iou) == aliceBalanceBefore);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Cover precision guard: Clawback");
|
||||
Env env{*this, features};
|
||||
auto const [brokerKeylet, iou] = setup(env);
|
||||
PrettyAmount const subUlpAmt = iou(Number{1, -16});
|
||||
auto const coverBefore = env.le(brokerKeylet)->at(sfCoverAvailable);
|
||||
env(coverClawback(issuer),
|
||||
kLoanBrokerId(brokerKeylet.key),
|
||||
kAmount(subUlpAmt),
|
||||
Ter(expected));
|
||||
env.close();
|
||||
if (expected == tesSUCCESS)
|
||||
{
|
||||
if (auto const broker = env.le(brokerKeylet); BEAST_EXPECT(broker))
|
||||
BEAST_EXPECT(broker->at(sfCoverAvailable) == coverBefore);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
runTestCases(all_);
|
||||
runTestCases(all_ - fixCleanup3_2_0);
|
||||
|
||||
// ============================================================
|
||||
// Asymmetry exploit (TER verdict): Deposit uses Downward rounding
|
||||
// in doApply (rejects when amount rounds to zero); Withdraw and
|
||||
// Clawback use ToNearest via canApplyToBrokerCover in preclaim
|
||||
// (rejects when amount is closer to zero than to one ULP).
|
||||
//
|
||||
// With sfCoverAvailable = 10 IOU, cover scale = -14:
|
||||
// half-ULP = 5e-15, one ULP = 1e-14.
|
||||
//
|
||||
// Probe = 6e-15 (above half-ULP, below one ULP):
|
||||
// Deposit Downward(6e-15, -14) = 0 → tecPRECISION_LOSS
|
||||
// Withdraw isZeroAtScale → ToNearest → 1e-14 ≠ 0 → tesSUCCESS
|
||||
// Clawback isZeroAtScale → ToNearest → 1e-14 ≠ 0 → tesSUCCESS
|
||||
//
|
||||
// Same amendment, same input, opposite TER across transactors.
|
||||
// Note: production doApply for Withdraw/Clawback consumes the
|
||||
// ORIGINAL 6e-15 (no roundToScale); the actual on-ledger delta is
|
||||
// 6e-15 because STAmount IOU has 16-digit mantissa precision and
|
||||
// 10 - 6e-15 = 9.999999999999994 fits exactly without rounding.
|
||||
// ============================================================
|
||||
{
|
||||
testcase("Cover precision asymmetry: Deposit rejects but Withdraw/Clawback accept");
|
||||
Env env{*this, all_};
|
||||
auto const [brokerKeylet, iou] = setup(env);
|
||||
|
||||
// 6e-15: above half-ULP, below one ULP at cover scale -14.
|
||||
PrettyAmount const probe = iou(Number{6, -15});
|
||||
|
||||
auto const coverInitial = env.le(brokerKeylet)->at(sfCoverAvailable);
|
||||
auto const aliceBalInitial = env.balance(alice, iou);
|
||||
|
||||
// (1) Deposit rejects under amendment ON.
|
||||
env(coverDeposit(alice, brokerKeylet.key, probe), Ter(tecPRECISION_LOSS));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(brokerKeylet)->at(sfCoverAvailable) == coverInitial);
|
||||
|
||||
// (2) Withdraw on identical input under same amendment: accepts.
|
||||
// Asymmetry: Deposit rejected this exact input, Withdraw
|
||||
// passes the guard and mutates state.
|
||||
env(coverWithdraw(alice, brokerKeylet.key, probe), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
auto const coverAfterWithdraw = env.le(brokerKeylet)->at(sfCoverAvailable);
|
||||
// doApply does `sfCoverAvailable -= amount` on the ORIGINAL
|
||||
// 6e-15. STAmount subtraction at 16-digit mantissa is exact
|
||||
// here, so the delta equals the input.
|
||||
BEAST_EXPECT((coverInitial - coverAfterWithdraw == Number{6, -15}));
|
||||
|
||||
// (3) Clawback on identical input under same amendment: accepts.
|
||||
env(coverClawback(issuer),
|
||||
kLoanBrokerId(brokerKeylet.key),
|
||||
kAmount(probe),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
auto const coverAfterClawback = env.le(brokerKeylet)->at(sfCoverAvailable);
|
||||
BEAST_EXPECT((coverAfterWithdraw - coverAfterClawback == Number{6, -15}));
|
||||
// Cumulative drift after Withdraw + Clawback.
|
||||
BEAST_EXPECT((coverInitial - coverAfterClawback == Number{12, -15}));
|
||||
|
||||
// Alice's IOU balance starts at ~990 (1000 minted - 10 deposited
|
||||
// as cover). Withdraw transfers 6e-15 from broker pseudo to
|
||||
// alice; Clawback transfers 6e-15 from broker pseudo to issuer
|
||||
// (NOT from alice). Adding 6e-15 to a 990 STAmount underflows
|
||||
// 16-digit precision (990 + 6e-15 → renormalizes back to 990),
|
||||
// so alice's stored balance is unchanged.
|
||||
BEAST_EXPECT(env.balance(alice, iou) == aliceBalInitial);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Asymmetry exploit (on-ledger delta): identical 1.8e-14 input
|
||||
// under amendment ON.
|
||||
//
|
||||
// Deposit: Downward pre-rounds 1.8e-14 → 1e-14 in doApply, then
|
||||
// sfCoverAvailable += 1e-14. delta = +1e-14.
|
||||
// Withdraw: guard passes (ToNearest(1.8e-14) = 2e-14 ≠ 0); doApply
|
||||
// does `sfCoverAvailable -= 1.8e-14` on the ORIGINAL.
|
||||
// STAmount IOU 16-digit mantissa: 10 - 1.8e-14 =
|
||||
// 9.999999999999982 (exact). delta = 1.8e-14.
|
||||
//
|
||||
// Same input, same amendment, 1.8x difference in effective
|
||||
// magnitude (1e-14 vs 1.8e-14). Trust-line vs sfCoverAvailable
|
||||
// invariant cannot be guaranteed under non-symmetric rounding.
|
||||
// ============================================================
|
||||
{
|
||||
testcase("Cover precision asymmetry: Deposit and Withdraw deltas diverge");
|
||||
Env env{*this, all_};
|
||||
auto const [brokerKeylet, iou] = setup(env);
|
||||
|
||||
PrettyAmount const probe = iou(Number{18, -15});
|
||||
|
||||
auto const coverPreDeposit = env.le(brokerKeylet)->at(sfCoverAvailable);
|
||||
env(coverDeposit(alice, brokerKeylet.key, probe), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
auto const coverPostDeposit = env.le(brokerKeylet)->at(sfCoverAvailable);
|
||||
Number const depositDelta = coverPostDeposit - coverPreDeposit;
|
||||
|
||||
env(coverWithdraw(alice, brokerKeylet.key, probe), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
auto const coverPostWithdraw = env.le(brokerKeylet)->at(sfCoverAvailable);
|
||||
Number const withdrawDelta = coverPostDeposit - coverPostWithdraw;
|
||||
|
||||
// Deposit Downward pre-round → +1e-14
|
||||
// Withdraw original amount, no round → -1.8e-14
|
||||
BEAST_EXPECT((depositDelta == Number{1, -14}));
|
||||
BEAST_EXPECT((withdrawDelta == Number{18, -15}));
|
||||
BEAST_EXPECT(depositDelta != withdrawDelta);
|
||||
}
|
||||
|
||||
// MPT amounts are integers; scale is 0; the guard never rejects a
|
||||
// positive integer amount. Verify all three callsites pass with amendment on.
|
||||
{
|
||||
testcase("Cover precision guard: MPT min amount passes");
|
||||
Env env{*this, all_};
|
||||
|
||||
env.fund(XRP(100'000), issuer, alice);
|
||||
env.close();
|
||||
|
||||
MPTTester mptt{env, issuer, kMptInitNoFund};
|
||||
mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
|
||||
env.close();
|
||||
|
||||
PrettyAsset const mptAsset = mptt["MPT"];
|
||||
mptt.authorize({.account = alice});
|
||||
env.close();
|
||||
|
||||
env(pay(issuer, alice, mptAsset(100)));
|
||||
env.close();
|
||||
|
||||
Vault const vault{env};
|
||||
auto [createTx, vaultKeylet] = vault.create({.owner = alice, .asset = mptAsset});
|
||||
env(createTx);
|
||||
env.close();
|
||||
|
||||
auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice));
|
||||
env(set(alice, vaultKeylet.key));
|
||||
env.close();
|
||||
|
||||
env(coverDeposit(alice, brokerKeylet.key, mptAsset(10)));
|
||||
env.close();
|
||||
|
||||
env(coverDeposit(alice, brokerKeylet.key, mptAsset(1)), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
env(coverWithdraw(alice, brokerKeylet.key, mptAsset(1)), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
env(coverClawback(issuer),
|
||||
kLoanBrokerId(brokerKeylet.key),
|
||||
kAmount(mptAsset(1)),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -1843,6 +2125,7 @@ public:
|
||||
testAmB06VaultFreezeCheckMissing();
|
||||
|
||||
testRIPD4274();
|
||||
testCoverPrecisionGuard();
|
||||
|
||||
// TODO: Write clawback failure tests with an issuer / MPT that doesn't
|
||||
// have the right flags set.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user