Merge remote-tracking branch 'upstream/develop' into sponsor

This commit is contained in:
tequ
2026-03-18 00:02:57 +09:00
52 changed files with 172 additions and 105 deletions

View File

@@ -207,14 +207,14 @@ jobs:
env:
CONFIG_NAME: ${{ inputs.config_name }}
run: |
ASAN_OPTS="halt_on_error=0:use_sigaltstack=0:print_stacktrace=1:detect_container_overflow=0:detect_stack_use_after_return=0:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/asan.supp"
ASAN_OPTS="include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-asan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/asan.supp"
if [[ "${CONFIG_NAME}" == *gcc* ]]; then
ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0"
fi
echo "ASAN_OPTIONS=${ASAN_OPTS}" >> ${GITHUB_ENV}
echo "TSAN_OPTIONS=second_deadlock_stack=1:halt_on_error=0:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/tsan.supp" >> ${GITHUB_ENV}
echo "UBSAN_OPTIONS=suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/ubsan.supp" >> ${GITHUB_ENV}
echo "LSAN_OPTIONS=suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/lsan.supp" >> ${GITHUB_ENV}
echo "TSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-tsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/tsan.supp" >> ${GITHUB_ENV}
echo "UBSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-ubsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/ubsan.supp" >> ${GITHUB_ENV}
echo "LSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-lsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/lsan.supp" >> ${GITHUB_ENV}
- name: Run the separate tests
if: ${{ !inputs.build_only }}

View File

@@ -112,6 +112,7 @@ words:
- gpgcheck
- gpgkey
- hotwallet
- hwaddress
- hwrap
- ifndef
- inequation

View File

@@ -89,8 +89,8 @@ cmake --build . --parallel 4
**IMPORTANT**: ASAN with Boost produces many false positives. Use these options:
```bash
export ASAN_OPTIONS="print_stacktrace=1:detect_container_overflow=0:suppressions=path/to/asan.supp:halt_on_error=0:log_path=asan.log"
export LSAN_OPTIONS="suppressions=path/to/lsan.supp:halt_on_error=0:log_path=lsan.log"
export ASAN_OPTIONS="include=sanitizers/suppressions/runtime-asan-options.txt:suppressions=sanitizers/suppressions/asan.supp"
export LSAN_OPTIONS="include=sanitizers/suppressions/runtime-lsan-options.txt:suppressions=sanitizers/suppressions/lsan.supp"
# Run tests
./xrpld --unittest --unittest-jobs=5
@@ -108,7 +108,7 @@ export LSAN_OPTIONS="suppressions=path/to/lsan.supp:halt_on_error=0:log_path=lsa
### ThreadSanitizer (TSan)
```bash
export TSAN_OPTIONS="suppressions=path/to/tsan.supp halt_on_error=0 log_path=tsan.log"
export TSAN_OPTIONS="include=sanitizers/suppressions/runtime-tsan-options.txt:suppressions=sanitizers/suppressions/tsan.supp"
# Run tests
./xrpld --unittest --unittest-jobs=5
@@ -129,7 +129,7 @@ More details [here](https://github.com/google/sanitizers/wiki/AddressSanitizerLe
### UndefinedBehaviorSanitizer (UBSan)
```bash
export UBSAN_OPTIONS="suppressions=path/to/ubsan.supp:print_stacktrace=1:halt_on_error=0:log_path=ubsan.log"
export UBSAN_OPTIONS="include=sanitizers/suppressions/runtime-ubsan-options.txt:suppressions=sanitizers/suppressions/ubsan.supp"
# Run tests
./xrpld --unittest --unittest-jobs=5

View File

@@ -1,5 +1,6 @@
#pragma once
#include <xrpl/basics/sanitizers.h>
#include <xrpl/beast/type_name.h>
#include <exception>
@@ -23,16 +24,28 @@ LogThrow(std::string const& title);
When called from within a catch block, it will pass
control to the next matching exception handler, if any.
Otherwise, std::terminate will be called.
ASAN can't handle sudden jumps in control flow very well. This
function is marked as XRPL_NO_SANITIZE_ADDRESS to prevent it from
triggering false positives, since it throws.
*/
[[noreturn]] inline void
[[noreturn]] XRPL_NO_SANITIZE_ADDRESS inline void
Rethrow()
{
LogThrow("Re-throwing exception");
throw;
}
/*
Logs and throws an exception of type E.
ASAN can't handle sudden jumps in control flow very well. This
function is marked as XRPL_NO_SANITIZE_ADDRESS to prevent it from
triggering false positives, since it throws.
*/
template <class E, class... Args>
[[noreturn]] inline void
[[noreturn]] XRPL_NO_SANITIZE_ADDRESS inline void
Throw(Args&&... args)
{
static_assert(

View File

@@ -0,0 +1,13 @@
#pragma once
// Helper to disable ASan/HwASan for specific functions
/*
ASAN flags some false positives with sudden jumps in control flow, like
exceptions, or when encountering coroutine stack switches. This macro can be used to disable ASAN
intrumentation for specific functions.
*/
#if defined(__GNUC__) || defined(__clang__)
#define XRPL_NO_SANITIZE_ADDRESS __attribute__((no_sanitize("address", "hwaddress")))
#else
#define XRPL_NO_SANITIZE_ADDRESS
#endif

View File

@@ -209,7 +209,7 @@ std::size_t constexpr maxDIDDocumentLength = 256;
std::size_t constexpr maxDIDURILength = 256;
/** The maximum length of an Attestation inside a DID */
std::size_t constexpr maxDIDAttestationLength = 256;
std::size_t constexpr maxDIDDataLength = 256;
/** The maximum length of a domain */
std::size_t constexpr maxDomainLength = 256;

View File

@@ -83,6 +83,9 @@ public:
std::uint32_t
getSeqValue() const;
AccountID
getFeePayer() const;
boost::container::flat_set<AccountID>
getMentionedAccounts() const;

View File

@@ -129,8 +129,7 @@ protected:
beast::Journal const j_;
AccountID const account_;
XRPAmount mPriorBalance; // Balance before fees.
XRPAmount mSourceBalance; // Balance after fees.
XRPAmount preFeeBalance_; // Balance before fees.
virtual ~Transactor() = default;
Transactor(Transactor const&) = delete;

View File

@@ -2,15 +2,6 @@
#
# ASAN_OPTIONS="print_stacktrace=1:detect_container_overflow=0:suppressions=sanitizers/suppressions/asan.supp:halt_on_error=0"
# Leaks in Doctest tests: xrpl.test.*
interceptor_name:src/libxrpl/net/HTTPClient.cpp
interceptor_name:src/libxrpl/net/RegisterSSLCerts.cpp
interceptor_name:src/tests/libxrpl/net/HTTPClient.cpp
interceptor_name:xrpl/net/AutoSocket.h
interceptor_name:xrpl/net/HTTPClient.h
interceptor_name:xrpl/net/HTTPClientSSLContext.h
interceptor_name:xrpl/net/RegisterSSLCerts.h
# Suppress false positive stack-buffer errors in thread stack allocation
# Related to ASan's __asan_handle_no_return warnings (github.com/google/sanitizers/issues/189)
# These occur during multi-threaded test initialization on macOS

View File

@@ -0,0 +1,8 @@
detect_container_overflow=false
detect_stack_use_after_return=false
debug=true
halt_on_error=false
print_stats=true
print_cmdline=true
use_sigaltstack=0
print_stacktrace=1

View File

@@ -0,0 +1 @@
halt_on_error=false

View File

@@ -0,0 +1,3 @@
halt_on_error=false
verbosity=1
second_deadlock_stack=1

View File

@@ -0,0 +1 @@
halt_on_error=false

View File

@@ -27,3 +27,11 @@ src:core/JobQueue.cpp
src:libxrpl/beast/utility/beast_Journal.cpp
src:test/beast/beast_PropertyStream_test.cpp
src:src/test/app/Invariants_test.cpp
# ASan false positive: stack-use-after-scope in ErrorCodes.h inline functions.
# When Clang inlines the StaticString overloads (e.g. invalid_field_error(StaticString)),
# ASan scope-poisons the temporary std::string before the inlined callee finishes reading
# through the const ref. This corrupts the coroutine stack and crashes the Simulate test.
# See asan.supp comments for full explanation and planned fix.
[address]
src:*ErrorCodes.h

View File

@@ -182,6 +182,17 @@ signed-integer-overflow:src/test/beast/LexicalCast_test.cpp
# External library suppressions
unsigned-integer-overflow:nudb/detail/xxhash.hpp
# Loan_test.cpp intentional underflow in test arithmetic
unsigned-integer-overflow:src/test/app/Loan_test.cpp
undefined:src/test/app/Loan_test.cpp
# Source tree restructured paths (libxrpl/tx/transactors/)
# These duplicate the xrpld/app/tx/detail entries above for the new layout
unsigned-integer-overflow:src/libxrpl/tx/transactors/oracle/SetOracle.cpp
undefined:src/libxrpl/tx/transactors/oracle/SetOracle.cpp
unsigned-integer-overflow:src/libxrpl/tx/transactors/nft/NFTokenMint.cpp
undefined:src/libxrpl/tx/transactors/nft/NFTokenMint.cpp
# Protobuf intentional overflows in hash functions
# Protobuf uses intentional unsigned overflow for hash computation (stringpiece.h:393)
unsigned-integer-overflow:google/protobuf/stubs/stringpiece.h

View File

@@ -258,7 +258,7 @@ Number::Guard::doRoundUp(
}
bringIntoRange(negative, mantissa, exponent, minMantissa);
if (exponent > maxExponent)
throw std::overflow_error(location);
Throw<std::overflow_error>(std::string(location));
}
template <UnsignedMantissa T>
@@ -298,7 +298,7 @@ Number::Guard::doRound(rep& drops, std::string location)
// or "(maxRep + 1) / 10", neither of which will round up when
// converting to rep, though the latter might overflow _before_
// rounding.
throw std::overflow_error(location); // LCOV_EXCL_LINE
Throw<std::overflow_error>(std::string(location)); // LCOV_EXCL_LINE
}
++drops;
}

View File

@@ -211,6 +211,20 @@ STTx::getSeqValue() const
return getSeqProxy().value();
}
AccountID
STTx::getFeePayer() const
{
// If sfDelegate is present, the delegate account is the payer
// note: if a delegate is specified, its authorization to act on behalf of the account is
// enforced in `Transactor::checkPermission`
// cryptographic signature validity is checked separately (e.g., in `Transactor::checkSign`)
if (isFieldPresent(sfDelegate))
return getAccountID(sfDelegate);
// Default payer
return getAccountID(sfAccount);
}
void
STTx::sign(
PublicKey const& publicKey,

View File

@@ -484,7 +484,6 @@ Transactor::payFee()
auto const feePaid = ctx_.tx[sfFee].xrp();
auto const payer = getFeePayer(view(), ctx_.tx);
auto const sle = view().peek(payer.entry);
JLOG(j_.trace()) << "Fee payer: " + to_string(payer.entry.key);
@@ -502,13 +501,7 @@ Transactor::payFee()
view().update(sle);
if (payer.type == FeePayerType::Account)
// Deduct the fee, so it's not available during the transaction.
// Will only write the account back if the transaction succeeds.
mSourceBalance -= feePaid;
// VFALCO Should we call view().rawDestroyXRP() here as well?
return tesSUCCESS;
}
@@ -708,8 +701,7 @@ Transactor::apply()
if (sle)
{
mPriorBalance = STAmount{(*sle)[sfBalance]}.xrp();
mSourceBalance = mPriorBalance;
preFeeBalance_ = STAmount{(*sle)[sfBalance]}.xrp();
TER result = consumeSeqProxy(sle);
if (result != tesSUCCESS)

View File

@@ -219,7 +219,7 @@ SponsorshipSet::doApply()
auto newSle = std::make_shared<SLE>(sponsorKeylet);
if (auto const ret = checkInsufficientReserve(
ctx_.view(), ctx_.tx, sponsorAccSle, mPriorBalance, reserveSponsorAccSle, 1);
ctx_.view(), ctx_.tx, sponsorAccSle, preFeeBalance_, reserveSponsorAccSle, 1);
!isTesSuccess(ret))
return tecUNFUNDED;

View File

@@ -400,10 +400,10 @@ DeleteAccount::doApply()
// Use the current balance from the SLE, not mSourceBalance, because
// the cleanup loop may have returned pre-funded sfFeeAmount from
// ltSponsorship objects back to the account's sfBalance.
auto const srcCurrentBalance = STAmount{(*src)[sfBalance]}.xrp();
(*dst)[sfBalance] = (*dst)[sfBalance] + srcCurrentBalance;
(*src)[sfBalance] = (*src)[sfBalance] - srcCurrentBalance;
ctx_.deliver(srcCurrentBalance);
auto const remainingBalance = src->getFieldAmount(sfBalance).xrp();
(*dst)[sfBalance] = (*dst)[sfBalance] + remainingBalance;
(*src)[sfBalance] = (*src)[sfBalance] - remainingBalance;
ctx_.deliver(remainingBalance);
if (src->isFieldPresent(sfSponsor))
{
@@ -445,7 +445,7 @@ DeleteAccount::doApply()
}
// Re-arm the password change fee if we can and need to.
if (srcCurrentBalance > XRPAmount(0) && dst->isFlag(lsfPasswordSpent))
if (remainingBalance > XRPAmount(0) && dst->isFlag(lsfPasswordSpent))
dst->clearFlag(lsfPasswordSpent);
view().update(dst);

View File

@@ -304,7 +304,7 @@ SetSignerList::replaceSignerList()
// with CreateTicket.
auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx);
if (auto const ret = checkInsufficientReserve(
ctx_.view(), ctx_.tx, sle, mPriorBalance, sponsor, addedOwnerCount);
ctx_.view(), ctx_.tx, sle, preFeeBalance_, sponsor, addedOwnerCount);
!isTesSuccess(ret))
return ret;

View File

@@ -337,7 +337,7 @@ enum class DepositAuthPolicy { normal, dstCanBypass };
struct TransferHelperSubmittingAccountInfo
{
AccountID account;
STAmount preFeeBalance;
STAmount preFeeBalance_;
STAmount postFeeBalance;
};
@@ -422,7 +422,7 @@ transferHelper(
if (!submittingAccountInfo || submittingAccountInfo->account != src ||
submittingAccountInfo->postFeeBalance != curBal)
return curBal;
return submittingAccountInfo->preFeeBalance;
return submittingAccountInfo->preFeeBalance_;
}();
if (availableBalance < amt + reserve)
@@ -1858,7 +1858,8 @@ XChainCommit::doApply()
auto const amount = ctx_.tx[sfAmount];
auto const bridgeSpec = ctx_.tx[sfXChainBridge];
if (!psb.read(keylet::account(account)))
auto const sleAccount = psb.read(keylet::account(account));
if (!sleAccount)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sleBridge = readBridge(psb, bridgeSpec);
@@ -1869,7 +1870,7 @@ XChainCommit::doApply()
// Support dipping into reserves to pay the fee
TransferHelperSubmittingAccountInfo submittingAccountInfo{
account_, mPriorBalance, mSourceBalance};
account_, preFeeBalance_, (*sleAccount)[sfBalance]};
auto const thTer = transferHelper(
psb,
@@ -2141,7 +2142,7 @@ XChainCreateAccountCommit::doApply()
// Support dipping into reserves to pay the fee
TransferHelperSubmittingAccountInfo submittingAccountInfo{
account_, mPriorBalance, mSourceBalance};
account_, preFeeBalance_, (*sle)[sfBalance]};
STAmount const toTransfer = amount + reward;
auto const thTer = transferHelper(
psb,

View File

@@ -316,7 +316,7 @@ CashCheck::doApply()
// Can the account cover the trust line's reserve?
if (auto const ret = checkInsufficientReserve(
psb, ctx_.tx, sleDst, mPriorBalance, sponsorSle, 1);
psb, ctx_.tx, sleDst, preFeeBalance_, sponsorSle, 1);
!isTesSuccess(ret))
{
JLOG(j_.trace()) << "Trust line does not exist. "

View File

@@ -138,7 +138,7 @@ CreateCheck::doApply()
// check the starting balance because we want to allow dipping into the
// reserve to pay fees.
auto const sponsor = getTxReserveSponsor(view(), ctx_.tx);
if (auto const ret = checkInsufficientReserve(view(), ctx_.tx, sle, mPriorBalance, sponsor, 1);
if (auto const ret = checkInsufficientReserve(view(), ctx_.tx, sle, preFeeBalance_, sponsor, 1);
!isTesSuccess(ret))
return ret;
// Note that we use the value from the sequence or ticket as the

View File

@@ -84,7 +84,7 @@ CredentialAccept::doApply()
auto const newSponsor = getTxReserveSponsor(view(), ctx_.tx);
if (auto const ret =
checkInsufficientReserve(view(), ctx_.tx, sleSubject, mPriorBalance, newSponsor, 1);
checkInsufficientReserve(view(), ctx_.tx, sleSubject, preFeeBalance_, newSponsor, 1);
!isTesSuccess(ret))
return ret;

View File

@@ -116,7 +116,7 @@ CredentialCreate::doApply()
auto const sponsor = getTxReserveSponsor(view(), ctx_.tx);
if (auto const ret =
checkInsufficientReserve(view(), ctx_.tx, sleIssuer, mPriorBalance, sponsor, 1);
checkInsufficientReserve(view(), ctx_.tx, sleIssuer, preFeeBalance_, sponsor, 1);
!isTesSuccess(ret))
return ret;

View File

@@ -69,7 +69,7 @@ DelegateSet::doApply()
auto const sponsor = getTxReserveSponsor(view(), ctx_.tx);
if (auto const ret =
checkInsufficientReserve(view(), ctx_.tx, sleOwner, mPriorBalance, sponsor, 1);
checkInsufficientReserve(view(), ctx_.tx, sleOwner, preFeeBalance_, sponsor, 1);
!isTesSuccess(ret))
return ret;

View File

@@ -173,7 +173,7 @@ AMMClawback::applyGuts(Sandbox& sb)
0,
FreezeHandling::fhIGNORE_FREEZE,
WithdrawAll::Yes,
mPriorBalance,
preFeeBalance_,
ctx_.journal);
else
std::tie(result, newLPTokenBalance, amountWithdraw, amount2Withdraw) =
@@ -253,7 +253,7 @@ AMMClawback::equalWithdrawMatchingOneAmount(
0,
FreezeHandling::fhIGNORE_FREEZE,
WithdrawAll::Yes,
mPriorBalance,
preFeeBalance_,
ctx_.journal);
auto const& rules = sb.rules();
@@ -285,7 +285,7 @@ AMMClawback::equalWithdrawMatchingOneAmount(
0,
FreezeHandling::fhIGNORE_FREEZE,
WithdrawAll::No,
mPriorBalance,
preFeeBalance_,
ctx_.journal);
}
@@ -305,7 +305,7 @@ AMMClawback::equalWithdrawMatchingOneAmount(
0,
FreezeHandling::fhIGNORE_FREEZE,
WithdrawAll::No,
mPriorBalance,
preFeeBalance_,
ctx_.journal);
}

View File

@@ -427,7 +427,7 @@ AMMWithdraw::withdraw(
tfee,
FreezeHandling::fhZERO_IF_FROZEN,
isWithdrawAll(ctx_.tx),
mPriorBalance,
preFeeBalance_,
j_);
return {ter, newLPTokenBalance};
}
@@ -660,7 +660,7 @@ AMMWithdraw::equalWithdrawTokens(
tfee,
FreezeHandling::fhZERO_IF_FROZEN,
isWithdrawAll(ctx_.tx),
mPriorBalance,
preFeeBalance_,
ctx_.journal);
return {ter, newLPTokenBalance};
}

View File

@@ -727,7 +727,7 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel)
{
auto const sponsor = getTxReserveSponsor(sb, ctx_.tx);
if (auto const ret =
checkInsufficientReserve(sb, ctx_.tx, sleCreator, mPriorBalance, sponsor, 1);
checkInsufficientReserve(sb, ctx_.tx, sleCreator, preFeeBalance_, sponsor, 1);
!isTesSuccess(ret))
{
// If we are here, the signing account had an insufficient reserve

View File

@@ -33,7 +33,7 @@ DIDDelete::deleteSLE(
if (!view.dirRemove(keylet::ownerDir(owner), (*sle)[sfOwnerNode], sle->key(), true))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete DID Token from owner.";
JLOG(j.fatal()) << "Unable to delete DID from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}

View File

@@ -41,13 +41,13 @@ DIDSet::preflight(PreflightContext const& ctx)
};
if (isTooLong(sfURI, maxDIDURILength) || isTooLong(sfDIDDocument, maxDIDDocumentLength) ||
isTooLong(sfData, maxDIDAttestationLength))
isTooLong(sfData, maxDIDDataLength))
return temMALFORMED;
return tesSUCCESS;
}
TER
static TER
addSLE(ApplyContext& ctx, std::shared_ptr<SLE> const& sle, AccountID const& owner)
{
auto const sleAccount = ctx.view().peek(keylet::account(owner));

View File

@@ -165,7 +165,7 @@ EscrowCancel::doApply()
ctx_.tx,
parityRate,
slep,
mPriorBalance,
preFeeBalance_,
amount,
issuer,
account, // sender and receiver are the same

View File

@@ -392,9 +392,9 @@ EscrowCreate::doApply()
// Check reserve and funds availability
STAmount const amount{ctx_.tx[sfAmount]};
auto const balance = sle->getFieldAmount(sfBalance).xrp();
auto const sponsor = getTxReserveSponsor(view(), ctx_.tx);
if (auto const ret =
checkInsufficientReserve(ctx_.view(), ctx_.tx, sle, mSourceBalance, sponsor, 1);
if (auto const ret = checkInsufficientReserve(ctx_.view(), ctx_.tx, sle, balance, sponsor, 1);
!isTesSuccess(ret))
return ret;
@@ -402,7 +402,7 @@ EscrowCreate::doApply()
if (isXRP(amount))
{
if (auto const ret = checkInsufficientReserve(
ctx_.view(), ctx_.tx, sle, mSourceBalance - STAmount(amount).xrp(), {}, 1);
ctx_.view(), ctx_.tx, sle, balance - STAmount(amount).xrp(), {}, 1);
!isTesSuccess(ret))
return tecUNFUNDED;
}

View File

@@ -333,7 +333,7 @@ EscrowFinish::doApply()
ctx_.tx,
lockedRate,
sled,
mPriorBalance,
preFeeBalance_,
amount,
issuer,
account,

View File

@@ -167,7 +167,7 @@ LoanBrokerCoverWithdraw::doApply()
associateAsset(*broker, vaultAsset);
return doWithdraw(view(), tx, account_, dstAcct, brokerPseudoID, mPriorBalance, amount, j_);
return doWithdraw(view(), tx, account_, dstAcct, brokerPseudoID, preFeeBalance_, amount, j_);
}
//------------------------------------------------------------------------------

View File

@@ -219,14 +219,14 @@ LoanBrokerSet::doApply()
auto const sponsor = getTxReserveSponsor(view, tx);
if (auto const ret =
checkInsufficientReserve(view, tx, owner, mPriorBalance, {}, sponsor ? 1 : 2);
checkInsufficientReserve(view, tx, owner, preFeeBalance_, {}, sponsor ? 1 : 2);
!isTesSuccess(ret))
return ret;
if (sponsor)
{
if (auto const ret =
checkInsufficientReserve(view, tx, owner, mPriorBalance, sponsor, 1);
checkInsufficientReserve(view, tx, owner, preFeeBalance_, sponsor, 1);
!isTesSuccess(ret))
return ret;
}
@@ -245,7 +245,7 @@ LoanBrokerSet::doApply()
auto pseudoId = pseudo->at(sfAccount);
if (auto ter =
addEmptyHolding(view, tx, pseudoId, mPriorBalance, sleVault->at(sfAsset), j_))
addEmptyHolding(view, tx, pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_))
return ter;
// Initialize data fields:

View File

@@ -84,12 +84,11 @@ LoanSet::preflight(PreflightContext const& ctx)
!validNumericMinimum(paymentInterval, LoanSet::minPaymentInterval))
return temINVALID;
// Grace period is between min default value and payment interval
else if (
auto const gracePeriod = tx[~sfGracePeriod]; //
!validNumericRange(
gracePeriod,
paymentInterval.value_or(LoanSet::defaultPaymentInterval),
defaultGracePeriod))
else if (auto const gracePeriod = tx[~sfGracePeriod]; //
!validNumericRange(
gracePeriod,
paymentInterval.value_or(LoanSet::defaultPaymentInterval),
defaultGracePeriod))
return temINVALID;
// Copied from preflight2
@@ -473,7 +472,7 @@ LoanSet::doApply()
auto const sponsorSle = getTxReserveSponsor(view, tx);
{
auto const balance =
account_ == borrower ? mPriorBalance : borrowerSle->at(sfBalance).value().xrp();
account_ == borrower ? preFeeBalance_ : borrowerSle->at(sfBalance).value().xrp();
if (auto const ret =
checkInsufficientReserve(view, tx, borrowerSle, balance, sponsorSle, 1);
!isTesSuccess(ret))

View File

@@ -363,7 +363,7 @@ NFTokenAcceptOffer::transferNFToken(
// NFTs free of reserve.
if (view().rules().enabled(fixNFTokenReserve))
{
// To check if there is sufficient reserve, we cannot use mPriorBalance
// To check if there is sufficient reserve, we cannot use preFeeBalance_
// because NFT is sold for a price. So we must use the balance after
// the deduction of the potential offer price. A small caveat here is
// that the balance has already deducted the transaction fee, meaning

View File

@@ -75,7 +75,7 @@ NFTokenCreateOffer::doApply()
ctx_.tx[~sfExpiration],
ctx_.tx.getSeqProxy(),
ctx_.tx[sfNFTokenID],
mPriorBalance,
preFeeBalance_,
j_,
ctx_.tx.getFlags());
}

View File

@@ -296,7 +296,7 @@ NFTokenMint::doApply()
ctx_.tx[~sfExpiration],
ctx_.tx.getSeqProxy(),
nftokenID,
mPriorBalance,
preFeeBalance_,
j_);
!isTesSuccess(ter))
return ter;
@@ -315,7 +315,7 @@ NFTokenMint::doApply()
ctx_.view(),
ctx_.tx,
view().read(keylet::account(account_)),
mPriorBalance,
preFeeBalance_,
sponsor,
0);
!isTesSuccess(ret))

View File

@@ -145,7 +145,7 @@ DepositPreauth::doApply()
// reserve to pay fees.
auto const sponsor = getTxReserveSponsor(view(), ctx_.tx);
if (auto const ret =
checkInsufficientReserve(view(), ctx_.tx, sleOwner, mPriorBalance, sponsor, 1);
checkInsufficientReserve(view(), ctx_.tx, sleOwner, preFeeBalance_, sponsor, 1);
!isTesSuccess(ret))
return ret;
@@ -191,7 +191,7 @@ DepositPreauth::doApply()
// reserve to pay fees.
auto const sponsor = getTxReserveSponsor(view(), ctx_.tx);
if (auto const ret =
checkInsufficientReserve(view(), ctx_.tx, sleOwner, mPriorBalance, sponsor, 1);
checkInsufficientReserve(view(), ctx_.tx, sleOwner, preFeeBalance_, sponsor, 1);
!isTesSuccess(ret))
return ret;

View File

@@ -582,16 +582,16 @@ Payment::doApply()
auto const reserve = calculateReserve(sleSrc, view().fees()) +
((txFlags & tfSponsorCreatedAccount) ? view().fees().reserve : beast::zero);
// mPriorBalance is the balance on the sending account BEFORE the
// preFeeBalance_ is the balance on the sending account BEFORE the
// fees were charged. We want to make sure we have enough reserve
// to send. Allow final spend to use reserve for fee.
auto const mmm = std::max(reserve, ctx_.tx.getFieldAmount(sfFee).xrp());
if (mPriorBalance < dstAmount.xrp() + mmm)
if (preFeeBalance_ < dstAmount.xrp() + mmm)
{
// Vote no. However the transaction might succeed, if applied in
// a different order.
JLOG(j_.trace()) << "Delay transaction: Insufficient funds: " << to_string(mPriorBalance)
JLOG(j_.trace()) << "Delay transaction: Insufficient funds: " << to_string(preFeeBalance_)
<< " / " << to_string(dstAmount.xrp() + mmm) << " (" << to_string(reserve)
<< ")";
@@ -639,7 +639,7 @@ Payment::doApply()
}
// Do the arithmetic for the transfer and make the ledger change.
sleSrc->setFieldAmount(sfBalance, mSourceBalance - dstAmount);
sleSrc->setFieldAmount(sfBalance, sleSrc->getFieldAmount(sfBalance) - dstAmount);
sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(sfBalance) + dstAmount);
// Re-arm the password change fee if we can and need to.

View File

@@ -63,7 +63,7 @@ CreateTicket::doApply()
std::uint32_t const ticketCount = ctx_.tx[sfTicketCount];
auto const sponsor = getTxReserveSponsor(view(), ctx_.tx);
if (auto const ret = checkInsufficientReserve(
view(), ctx_.tx, sleAccountRoot, mPriorBalance, sponsor, ticketCount);
view(), ctx_.tx, sleAccountRoot, preFeeBalance_, sponsor, ticketCount);
!isTesSuccess(ret))
return ret;

View File

@@ -162,7 +162,7 @@ MPTokenAuthorize::doApply()
return authorizeMPToken(
ctx_.view(),
tx,
mPriorBalance,
preFeeBalance_,
tx[sfMPTokenIssuanceID],
account_,
ctx_.journal,

View File

@@ -151,7 +151,7 @@ MPTokenIssuanceCreate::doApply()
tx,
j_,
{
.priorBalance = mPriorBalance,
.priorBalance = preFeeBalance_,
.account = account_,
.sequence = tx.getSeqValue(),
.flags = tx.getFlags(),

View File

@@ -542,7 +542,7 @@ SetTrust::doApply()
{
// should be checked PreFunded Sponsor before adjustOwnerCount()
if (auto const ret = checkInsufficientReserve(
view(), ctx_.tx, sleLowAccount, mPriorBalance, txSponsorSle, 1);
view(), ctx_.tx, sleLowAccount, preFeeBalance_, txSponsorSle, 1);
isSponsoredAndPreFunded && !isTesSuccess(ret))
return tecINSUF_RESERVE_LINE;
@@ -569,7 +569,7 @@ SetTrust::doApply()
{
// should be checked PreFunded Sponsor before adjustOwnerCount()
if (auto const ret = checkInsufficientReserve(
view(), ctx_.tx, sleHighAccount, mPriorBalance, txSponsorSle, 1);
view(), ctx_.tx, sleHighAccount, preFeeBalance_, txSponsorSle, 1);
isSponsoredAndPreFunded && !isTesSuccess(ret))
return tecINSUF_RESERVE_LINE;
@@ -602,8 +602,8 @@ SetTrust::doApply()
terResult = trustDelete(view(), sleRippleState, uLowAccountID, uHighAccountID, viewJ);
}
// Reserve is not scaled by load.
else if (auto const ret =
checkInsufficientReserve(view(), ctx_.tx, sle, mPriorBalance, txSponsorSle, 0);
else if (auto const ret = checkInsufficientReserve(
view(), ctx_.tx, sle, preFeeBalance_, txSponsorSle, 0);
!freeTrustLine && bReserveIncrease && !isTesSuccess(ret))
{
JLOG(j_.trace()) << "Delay transaction: Insufficent reserve to "
@@ -636,7 +636,7 @@ SetTrust::doApply()
ctx_.view(),
ctx_.tx,
sle,
mPriorBalance,
preFeeBalance_,
txSponsorSle,
1);
!freeTrustLine && !isTesSuccess(ret)) // Reserve is not scaled by load.

View File

@@ -140,14 +140,16 @@ VaultCreate::doApply()
{
adjustOwnerCount(view(), owner, sponsor, 2, j_);
addSponsorToLedgerEntry(vault, sponsor);
if (auto const ret = checkInsufficientReserve(view(), tx, owner, mPriorBalance, sponsor, 0);
if (auto const ret =
checkInsufficientReserve(view(), tx, owner, preFeeBalance_, sponsor, 0);
!isTesSuccess(ret))
return ret;
}
else
{
// after Sponsor Amendment, check insufficient reserve first
if (auto const ret = checkInsufficientReserve(view(), tx, owner, mPriorBalance, sponsor, 2);
if (auto const ret =
checkInsufficientReserve(view(), tx, owner, preFeeBalance_, sponsor, 2);
!isTesSuccess(ret))
return ret;
adjustOwnerCount(view(), owner, sponsor, 2, j_);
@@ -161,7 +163,7 @@ VaultCreate::doApply()
auto pseudoId = pseudo->at(sfAccount);
auto asset = tx[sfAsset];
if (auto ter = addEmptyHolding(view(), tx, pseudoId, mPriorBalance, asset, j_);
if (auto ter = addEmptyHolding(view(), tx, pseudoId, preFeeBalance_, asset, j_);
!isTesSuccess(ter))
return ter;
@@ -222,7 +224,7 @@ VaultCreate::doApply()
// Explicitly create MPToken for the vault owner
if (auto const err =
authorizeMPToken(view(), tx, mPriorBalance, mptIssuanceID, account_, ctx_.journal);
authorizeMPToken(view(), tx, preFeeBalance_, mptIssuanceID, account_, ctx_.journal);
!isTesSuccess(err))
return err;
@@ -230,7 +232,7 @@ VaultCreate::doApply()
if (txFlags & tfVaultPrivate)
{
if (auto const err = authorizeMPToken(
view(), tx, mPriorBalance, mptIssuanceID, pseudoId, ctx_.journal, {}, account_);
view(), tx, preFeeBalance_, mptIssuanceID, pseudoId, ctx_.journal, {}, account_);
!isTesSuccess(err))
return err;
}

View File

@@ -146,7 +146,7 @@ VaultDeposit::doApply()
if (vault->isFlag(lsfVaultPrivate) && account_ != vault->at(sfOwner))
{
if (auto const err = enforceMPTokenAuthorization(
ctx_.view(), ctx_.tx, mptIssuanceID, account_, mPriorBalance, j_);
ctx_.view(), ctx_.tx, mptIssuanceID, account_, preFeeBalance_, j_);
!isTesSuccess(err))
return err;
}
@@ -156,7 +156,12 @@ VaultDeposit::doApply()
if (!view().exists(keylet::mptoken(mptIssuanceID, account_)))
{
if (auto const err = authorizeMPToken(
view(), ctx_.tx, mPriorBalance, mptIssuanceID->value(), account_, ctx_.journal);
view(),
ctx_.tx,
preFeeBalance_,
mptIssuanceID->value(),
account_,
ctx_.journal);
!isTesSuccess(err))
return err;
}
@@ -170,7 +175,7 @@ VaultDeposit::doApply()
if (auto const err = authorizeMPToken(
view(),
ctx_.tx,
mPriorBalance, // priorBalance
preFeeBalance_, // priorBalance
mptIssuanceID->value(), // mptIssuanceID
sleIssuance->at(sfIssuer), // account
ctx_.journal,

View File

@@ -233,7 +233,7 @@ VaultWithdraw::doApply()
associateAsset(*vault, vaultAsset);
return doWithdraw(
view(), ctx_.tx, account_, dstAcct, vaultAccount, mPriorBalance, assetsWithdrawn, j_);
view(), ctx_.tx, account_, dstAcct, vaultAccount, preFeeBalance_, assetsWithdrawn, j_);
}
} // namespace xrpl

View File

@@ -189,7 +189,7 @@ struct DID_test : public beast::unit_test::suite
Account const edna{"edna"};
Account const francis{"francis"};
Account const george{"george"};
env.fund(XRP(5000), alice, bob, charlie, dave, edna, francis);
env.fund(XRP(5000), alice, bob, charlie, dave, edna, francis, george);
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(ownerCount(env, bob) == 0);
@@ -355,12 +355,14 @@ struct DID_test : public beast::unit_test::suite
testAccountReserve(all);
testSetInvalid(all);
testDeleteInvalid(all);
testSetValidInitial(all);
testSetModify(all);
testEnabled(all - emptyDID);
testAccountReserve(all - emptyDID);
testSetInvalid(all - emptyDID);
testDeleteInvalid(all - emptyDID);
testSetValidInitial(all - emptyDID);
testSetModify(all - emptyDID);
}
};

View File

@@ -53,7 +53,7 @@ public:
}
};
/** Sets the optional Attestation on a DIDSet. */
/** Sets the optional Data on a DIDSet. */
class data
{
private: