diff --git a/include/xrpl/protocol/ConfidentialTransfer.h b/include/xrpl/protocol/ConfidentialTransfer.h index 98a834f42e..35d31aa129 100644 --- a/include/xrpl/protocol/ConfidentialTransfer.h +++ b/include/xrpl/protocol/ConfidentialTransfer.h @@ -15,6 +15,9 @@ #include +#include +#include + namespace xrpl { /** @@ -61,10 +64,12 @@ struct EcPair inline void incrementConfidentialVersion(STObject& mptoken) { - // Retrieve current version and increment. - // Unsigned integer overflow is defined behavior in C++ (wraps to 0), - // which is acceptable here. - mptoken[sfConfidentialBalanceVersion] = mptoken[~sfConfidentialBalanceVersion].valueOr(0u) + 1u; + // Retrieve current version and increment, wrapping back to 0 at UINT32_MAX. + // The wrap is computed explicitly rather than relying on unsigned overflow + // of `+ 1u`, as it trips the unsigned-integer-overflow sanitizer in the UBSan CI build. + auto const current = mptoken[~sfConfidentialBalanceVersion].valueOr(0u); + mptoken[sfConfidentialBalanceVersion] = + current == std::numeric_limits::max() ? 0u : current + 1u; } /** diff --git a/src/libxrpl/tx/invariants/MPTInvariant.cpp b/src/libxrpl/tx/invariants/MPTInvariant.cpp index 05370e04b8..c01e1c0e4b 100644 --- a/src/libxrpl/tx/invariants/MPTInvariant.cpp +++ b/src/libxrpl/tx/invariants/MPTInvariant.cpp @@ -506,10 +506,16 @@ ValidConfidentialMPToken::visitEntry( return beast::kZero; }; + // Clamp to the cap (== INT64_MAX) before the signed conversion. We do INT64_MAX + 1 + // which the invariant tests inject, which would result in undefined behavior (under UBSan). + auto const toSignedAmount = [](std::uint64_t v) -> std::int64_t { + return static_cast(std::min(v, kMaxMpTokenAmount)); + }; + if (before && before->getType() == ltMPTOKEN) { uint192 const id = getMptID(before); - changes_[id].mptAmountDelta -= static_cast(before->getFieldU64(sfMPTAmount)); + changes_[id].mptAmountDelta -= toSignedAmount(before->getFieldU64(sfMPTAmount)); // Cannot delete MPToken with non-zero confidential state or non-zero public amount if (isDelete) @@ -527,7 +533,7 @@ ValidConfidentialMPToken::visitEntry( if (after && after->getType() == ltMPTOKEN) { uint192 const id = getMptID(after); - changes_[id].mptAmountDelta += static_cast(after->getFieldU64(sfMPTAmount)); + changes_[id].mptAmountDelta += toSignedAmount(after->getFieldU64(sfMPTAmount)); // Encrypted field existence consistency bool const hasIssuerBalance = after->isFieldPresent(sfIssuerEncryptedBalance); @@ -565,10 +571,9 @@ ValidConfidentialMPToken::visitEntry( if (before->isFieldPresent(sfConfidentialOutstandingAmount)) { changes_[id].coaDelta -= - static_cast(before->getFieldU64(sfConfidentialOutstandingAmount)); + toSignedAmount(before->getFieldU64(sfConfidentialOutstandingAmount)); } - changes_[id].outstandingDelta -= - static_cast(before->getFieldU64(sfOutstandingAmount)); + changes_[id].outstandingDelta -= toSignedAmount(before->getFieldU64(sfOutstandingAmount)); } if (after && after->getType() == ltMPTOKEN_ISSUANCE) @@ -581,9 +586,9 @@ ValidConfidentialMPToken::visitEntry( std::uint64_t const oa = after->getFieldU64(sfOutstandingAmount); if (hasCOA) - change.coaDelta += static_cast(coa); + change.coaDelta += toSignedAmount(coa); - change.outstandingDelta += static_cast(oa); + change.outstandingDelta += toSignedAmount(oa); change.issuance = after; // COA <= OutstandingAmount