diff --git a/include/xrpl/beast/utility/instrumentation.h b/include/xrpl/beast/utility/instrumentation.h index 39b80bc438..c20c42156a 100644 --- a/include/xrpl/beast/utility/instrumentation.h +++ b/include/xrpl/beast/utility/instrumentation.h @@ -11,6 +11,8 @@ // Macros below are copied from antithesis_sdk.h and slightly simplified // The duplication is because Visual Studio 2019 cannot compile that header // even with the option -Zc:__cplusplus added. +// NOTE: cond must not contain bare commas outside () or []. Commas inside {} +// are not protected by the preprocessor and would be parsed as extra arguments. #define ALWAYS(cond, message, ...) assert((message) && (cond)) #define ALWAYS_OR_UNREACHABLE(cond, message) assert((message) && (cond)) #define SOMETIMES(cond, message, ...) @@ -22,6 +24,8 @@ #define XRPL_ASSERT_PARTS(cond, function, description, ...) \ XRPL_ASSERT(cond, function " : " description) +#define XRPL_ASSERT_IF(guard, cond, message) XRPL_ASSERT(!(guard) || (cond), message) + // How to use the instrumentation macros: // // * XRPL_ASSERT if cond must be true but the line might not be reached during @@ -29,6 +33,14 @@ // * XRPL_ASSERT_PARTS is for convenience, and works like XRPL_ASSERT, but // splits the message param into "function" and "description", then joins // them with " : " before passing to XRPL_ASSERT. +// * XRPL_ASSERT_IF(guard, cond, message) asserts the implication +// `guard => cond`: it can only fail when guard is true (e.g. an amendment +// is enabled) and cond is false. Unlike `if (guard) XRPL_ASSERT(...)`, the +// assertion site is always evaluated, so the fuzzer registers it +// unconditionally; cond itself is short-circuited and only evaluated when +// guard is true. NOTE: do not rely on side effects in guard — in release +// builds the assertion body is stripped, and the compiler may optimize away +// a side-effect-free guard entirely. // * ALWAYS if cond must be true _and_ the line must be reached during fuzzing. // Same like `assert` in normal use. // * REACHABLE if the line must be reached during fuzzing diff --git a/src/libxrpl/ledger/helpers/LendingHelpers.cpp b/src/libxrpl/ledger/helpers/LendingHelpers.cpp index 15ebf33e46..0a195f9cbe 100644 --- a/src/libxrpl/ledger/helpers/LendingHelpers.cpp +++ b/src/libxrpl/ledger/helpers/LendingHelpers.cpp @@ -798,44 +798,44 @@ doOverpayment( // (P * factor) / factor round-trip can leave the new principal one // scale-unit high, so these equalities do not hold on the pre-amendment // code path and must be gated to match the fix they verify. - if (rules.enabled(fixCleanup3_2_0)) - { - // The valueChange returned by tryOverpayment satisfies - // valueChange = (newInterestDue - oldInterestDue) + untrackedInterest. - // Using the loan-state identity v = p + i + m and the adjacent - // `principal change agrees` assertion (dp = oldP - newP), this - // rearranges into three independently-computable terms: - // - // 1. TVO change beyond what principal repayment alone explains: - // newTVO - (oldTVO - dp) - // 2. Management fee released by re-amortization (positive when - // mfee decreased; zero when managementFeeRate == 0): - // oldMfee - newMfee - // 3. The overpayment's penalty interest part (= untrackedInterest - // for the overpayment path; see computeOverpaymentComponents): - // trackedInterestPart() - [[maybe_unused]] Number const tvoChange = newRoundedLoanState.valueOutstanding - - (totalValueOutstandingProxy - overpaymentComponents.trackedPrincipalDelta); - [[maybe_unused]] Number const managementFeeReleased = - managementFeeOutstandingProxy - newRoundedLoanState.managementFeeDue; - [[maybe_unused]] Number const interestPart = overpaymentComponents.trackedInterestPart(); + // + // The valueChange returned by tryOverpayment satisfies + // valueChange = (newInterestDue - oldInterestDue) + untrackedInterest. + // Using the loan-state identity v = p + i + m and the adjacent + // `principal change agrees` assertion (dp = oldP - newP), this + // rearranges into three independently-computable terms: + // + // 1. TVO change beyond what principal repayment alone explains: + // newTVO - (oldTVO - dp) + // 2. Management fee released by re-amortization (positive when + // mfee decreased; zero when managementFeeRate == 0): + // oldMfee - newMfee + // 3. The overpayment's penalty interest part (= untrackedInterest + // for the overpayment path; see computeOverpaymentComponents): + // trackedInterestPart() + bool const fix320Enabled = rules.enabled(fixCleanup3_2_0); + XRPL_ASSERT_IF( + fix320Enabled, + overpaymentComponents.trackedPrincipalDelta == + principalOutstandingProxy - newRoundedLoanState.principalOutstanding, + "xrpl::detail::doOverpayment : principal change agrees"); - XRPL_ASSERT_PARTS( - overpaymentComponents.trackedPrincipalDelta == - principalOutstandingProxy - newRoundedLoanState.principalOutstanding, - "xrpl::detail::doOverpayment", - "principal change agrees"); + XRPL_ASSERT_IF( + fix320Enabled, + [&] { + Number const tvoChange = newRoundedLoanState.valueOutstanding - + (totalValueOutstandingProxy - overpaymentComponents.trackedPrincipalDelta); + Number const managementFeeReleased = + managementFeeOutstandingProxy - newRoundedLoanState.managementFeeDue; + Number const interestPart = overpaymentComponents.trackedInterestPart(); + return loanPaymentParts.valueChange == tvoChange + managementFeeReleased + interestPart; + }(), + "xrpl::detail::doOverpayment : interest paid agrees"); - XRPL_ASSERT_PARTS( - loanPaymentParts.valueChange == tvoChange + managementFeeReleased + interestPart, - "xrpl::detail::doOverpayment", - "interest paid agrees"); - - XRPL_ASSERT_PARTS( - overpaymentComponents.trackedPrincipalDelta == loanPaymentParts.principalPaid, - "xrpl::detail::doOverpayment", - "principal payment matches"); - } + XRPL_ASSERT_IF( + fix320Enabled, + overpaymentComponents.trackedPrincipalDelta == loanPaymentParts.principalPaid, + "xrpl::detail::doOverpayment : principal payment matches"); // All validations passed, so update the proxy objects (which will // modify the actual Loan ledger object) @@ -1326,13 +1326,11 @@ computeOverpaymentComponents( TenthBips32 const overpaymentFeeRate, TenthBips16 const managementFeeRate) { - if (rules.enabled(fixCleanup3_2_0)) - { - XRPL_ASSERT( - overpayment > 0 && isRounded(asset, overpayment, loanScale), - "xrpl::detail::computeOverpaymentComponents : valid overpayment " - "amount"); - } + XRPL_ASSERT_IF( + rules.enabled(fixCleanup3_2_0), + overpayment > 0 && isRounded(asset, overpayment, loanScale), + "xrpl::detail::computeOverpaymentComponents : valid overpayment " + "amount"); // First, deduct the fixed overpayment fee from the total amount. // This reduces the effective payment that will be applied to the loan. diff --git a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp index 387116d820..8b3385471d 100644 --- a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp @@ -736,10 +736,10 @@ unlockEscrowMPT( STAmount const& grossAmount, beast::Journal j) { - if (!view.rules().enabled(fixTokenEscrowV1)) - { - XRPL_ASSERT(netAmount == grossAmount, "xrpl::unlockEscrowMPT : netAmount == grossAmount"); - } + XRPL_ASSERT_IF( + !view.rules().enabled(fixTokenEscrowV1), + netAmount == grossAmount, + "xrpl::unlockEscrowMPT : netAmount == grossAmount"); auto const& issuer = netAmount.getIssuer(); auto const& mptIssue = netAmount.get();