From f53029e474764f5c2346c7aa026cc7e4b6e0c8fe Mon Sep 17 00:00:00 2001 From: tequ Date: Fri, 3 Oct 2025 23:13:49 +0900 Subject: [PATCH 1/9] refactor: checking Insufficient reserve to use helper function --- include/xrpl/ledger/View.h | 11 ++++++ src/libxrpl/ledger/View.cpp | 38 +++++++++++++++---- src/xrpld/app/tx/detail/AMMWithdraw.cpp | 16 ++++---- src/xrpld/app/tx/detail/CreateCheck.cpp | 11 ++---- src/xrpld/app/tx/detail/CreateTicket.cpp | 11 ++---- src/xrpld/app/tx/detail/Credentials.cpp | 20 ++++------ src/xrpld/app/tx/detail/DID.cpp | 13 +++---- src/xrpld/app/tx/detail/DelegateSet.cpp | 9 ++--- src/xrpld/app/tx/detail/DepositPreauth.cpp | 22 ++++------- src/xrpld/app/tx/detail/Escrow.cpp | 22 +++++------ .../app/tx/detail/MPTokenIssuanceCreate.cpp | 11 ++++-- .../app/tx/detail/NFTokenAcceptOffer.cpp | 13 +++++-- src/xrpld/app/tx/detail/NFTokenMint.cpp | 16 +++++--- src/xrpld/app/tx/detail/NFTokenUtils.cpp | 7 ++-- src/xrpld/app/tx/detail/PayChan.cpp | 30 ++++++++------- .../app/tx/detail/PermissionedDomainSet.cpp | 8 ++-- src/xrpld/app/tx/detail/SetOracle.cpp | 8 ++-- src/xrpld/app/tx/detail/SetSignerList.cpp | 9 ++--- src/xrpld/app/tx/detail/VaultCreate.cpp | 9 +++-- src/xrpld/app/tx/detail/XChainBridge.cpp | 27 ++++++------- 20 files changed, 171 insertions(+), 140 deletions(-) diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index 9698b4fda39..178b421b587 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -451,6 +451,17 @@ areCompatible( beast::Journal::Stream& s, char const* reason); +uint32_t +ownerCount(std::shared_ptr const& accountSle); + +TER +checkInsufficientReserve( + ReadView const& view, + std::shared_ptr const& accSle, + STAmount const& accBalance, + std::int32_t ownerCountDelta, + std::int32_t accountCountDelta = 0); + //------------------------------------------------------------------------------ // // Modifiers diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index 10b7e81b4f6..170be487bd5 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -1019,6 +1019,30 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal) return std::nullopt; } +uint32_t +ownerCount(std::shared_ptr const& accountSle) +{ + auto const ownerCount = accountSle->getFieldU32(sfOwnerCount); + return ownerCount; +} + +TER +checkInsufficientReserve( + ReadView const& view, + std::shared_ptr const& accSle, + STAmount const& accBalance, + std::int32_t ownerCountDelta, + std::int32_t accountCountDelta) +{ + STAmount const reserve{view.fees().accountReserve( + accSle->getFieldU32(sfOwnerCount) + ownerCountDelta)}; + + if (accBalance < reserve) + return tecINSUFFICIENT_RESERVE; + + return tesSUCCESS; +} + //------------------------------------------------------------------------------ // // Modifiers @@ -1328,13 +1352,13 @@ authorizeMPToken( // an account owns, in the case of MPTokens we only // *enforce* a reserve if the user owns more than two // items. This is similar to the reserve requirements of trust lines. - std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount); - XRPAmount const reserveCreate( - (uOwnerCount < 2) ? XRPAmount(beast::zero) - : view.fees().accountReserve(uOwnerCount + 1)); - - if (priorBalance < reserveCreate) - return tecINSUFFICIENT_RESERVE; + if (ownerCount(sleAcct) >= 2) + { + if (auto const ret = + checkInsufficientReserve(view, sleAcct, priorBalance, 1); + !isTesSuccess(ret)) + return ret; + } auto const mptokenKey = keylet::mptoken(mptIssuanceID, account); auto mptoken = std::make_shared(mptokenKey); diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.cpp b/src/xrpld/app/tx/detail/AMMWithdraw.cpp index f5af9dfb9c4..c8507d83376 100644 --- a/src/xrpld/app/tx/detail/AMMWithdraw.cpp +++ b/src/xrpld/app/tx/detail/AMMWithdraw.cpp @@ -590,15 +590,15 @@ AMMWithdraw::withdraw( if (!sleAccount) return tecINTERNAL; // LCOV_EXCL_LINE auto const balance = (*sleAccount)[sfBalance].xrp(); - std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount); - // See also SetTrust::doApply() - XRPAmount const reserve( - (ownerCount < 2) ? XRPAmount(beast::zero) - : view.fees().accountReserve(ownerCount + 1)); - - if (std::max(priorBalance, balance) < reserve) - return tecINSUFFICIENT_RESERVE; + std::uint32_t const count = ownerCount(sleAccount); + if (count >= 2) + { + if (auto const ret = checkInsufficientReserve( + view, sleAccount, std::max(priorBalance, balance), 1); + !isTesSuccess(ret)) + return ret; + } } return tesSUCCESS; }; diff --git a/src/xrpld/app/tx/detail/CreateCheck.cpp b/src/xrpld/app/tx/detail/CreateCheck.cpp index 57f3a92255a..9c9a53223af 100644 --- a/src/xrpld/app/tx/detail/CreateCheck.cpp +++ b/src/xrpld/app/tx/detail/CreateCheck.cpp @@ -164,13 +164,10 @@ CreateCheck::doApply() // A check counts against the reserve of the issuing account, but we // check the starting balance because we want to allow dipping into the // reserve to pay fees. - { - STAmount const reserve{ - view().fees().accountReserve(sle->getFieldU32(sfOwnerCount) + 1)}; - - if (mPriorBalance < reserve) - return tecINSUFFICIENT_RESERVE; - } + if (auto const ret = + checkInsufficientReserve(view(), sle, mPriorBalance, 1); + !isTesSuccess(ret)) + return ret; // Note that we use the value from the sequence or ticket as the // Check sequence. For more explanation see comments in SeqProxy.h. diff --git a/src/xrpld/app/tx/detail/CreateTicket.cpp b/src/xrpld/app/tx/detail/CreateTicket.cpp index d48da2d780c..a3498e8c995 100644 --- a/src/xrpld/app/tx/detail/CreateTicket.cpp +++ b/src/xrpld/app/tx/detail/CreateTicket.cpp @@ -82,13 +82,10 @@ CreateTicket::doApply() // check the starting balance because we want to allow dipping into the // reserve to pay fees. std::uint32_t const ticketCount = ctx_.tx[sfTicketCount]; - { - XRPAmount const reserve = view().fees().accountReserve( - sleAccountRoot->getFieldU32(sfOwnerCount) + ticketCount); - - if (mPriorBalance < reserve) - return tecINSUFFICIENT_RESERVE; - } + if (auto const ret = checkInsufficientReserve( + view(), sleAccountRoot, mPriorBalance, ticketCount); + !isTesSuccess(ret)) + return ret; beast::Journal viewJ{ctx_.app.journal("View")}; diff --git a/src/xrpld/app/tx/detail/Credentials.cpp b/src/xrpld/app/tx/detail/Credentials.cpp index 4b77163c5db..f7d30ccdbea 100644 --- a/src/xrpld/app/tx/detail/Credentials.cpp +++ b/src/xrpld/app/tx/detail/Credentials.cpp @@ -139,12 +139,10 @@ CredentialCreate::doApply() if (!sleIssuer) return tefINTERNAL; - { - STAmount const reserve{view().fees().accountReserve( - sleIssuer->getFieldU32(sfOwnerCount) + 1)}; - if (mPriorBalance < reserve) - return tecINSUFFICIENT_RESERVE; - } + if (auto const ret = + checkInsufficientReserve(view(), sleIssuer, mPriorBalance, 1); + !isTesSuccess(ret)) + return ret; sleCred->setAccountID(sfSubject, subject); sleCred->setAccountID(sfIssuer, account_); @@ -344,12 +342,10 @@ CredentialAccept::doApply() if (!sleSubject || !sleIssuer) return tefINTERNAL; - { - STAmount const reserve{view().fees().accountReserve( - sleSubject->getFieldU32(sfOwnerCount) + 1)}; - if (mPriorBalance < reserve) - return tecINSUFFICIENT_RESERVE; - } + if (auto const ret = + checkInsufficientReserve(view(), sleSubject, mPriorBalance, 1); + !isTesSuccess(ret)) + return ret; auto const credType(ctx_.tx[sfCredentialType]); Keylet const credentialKey = keylet::credential(account_, issuer, credType); diff --git a/src/xrpld/app/tx/detail/DID.cpp b/src/xrpld/app/tx/detail/DID.cpp index b38b207d36c..33b0d508272 100644 --- a/src/xrpld/app/tx/detail/DID.cpp +++ b/src/xrpld/app/tx/detail/DID.cpp @@ -79,14 +79,11 @@ addSLE( return tefINTERNAL; // Check reserve availability for new object creation - { - auto const balance = STAmount((*sleAccount)[sfBalance]).xrp(); - auto const reserve = - ctx.view().fees().accountReserve((*sleAccount)[sfOwnerCount] + 1); - - if (balance < reserve) - return tecINSUFFICIENT_RESERVE; - } + auto const balance = STAmount((*sleAccount)[sfBalance]).xrp(); + if (auto const ret = + checkInsufficientReserve(ctx.view(), sleAccount, balance, 1); + !isTesSuccess(ret)) + return ret; // Add ledger object to ledger ctx.view().insert(sle); diff --git a/src/xrpld/app/tx/detail/DelegateSet.cpp b/src/xrpld/app/tx/detail/DelegateSet.cpp index e769f75d8ae..dbca9954c82 100644 --- a/src/xrpld/app/tx/detail/DelegateSet.cpp +++ b/src/xrpld/app/tx/detail/DelegateSet.cpp @@ -109,11 +109,10 @@ DelegateSet::doApply() return tesSUCCESS; } - STAmount const reserve{ctx_.view().fees().accountReserve( - sleOwner->getFieldU32(sfOwnerCount) + 1)}; - - if (mPriorBalance < reserve) - return tecINSUFFICIENT_RESERVE; + if (auto const ret = + checkInsufficientReserve(view(), sleOwner, mPriorBalance, 1); + !isTesSuccess(ret)) + return ret; auto const& permissions = ctx_.tx.getFieldArray(sfPermissions); if (!permissions.empty()) diff --git a/src/xrpld/app/tx/detail/DepositPreauth.cpp b/src/xrpld/app/tx/detail/DepositPreauth.cpp index 236b59a173b..d8c624556a6 100644 --- a/src/xrpld/app/tx/detail/DepositPreauth.cpp +++ b/src/xrpld/app/tx/detail/DepositPreauth.cpp @@ -170,13 +170,10 @@ DepositPreauth::doApply() // A preauth counts against the reserve of the issuing account, but we // check the starting balance because we want to allow dipping into the // reserve to pay fees. - { - STAmount const reserve{view().fees().accountReserve( - sleOwner->getFieldU32(sfOwnerCount) + 1)}; - - if (mPriorBalance < reserve) - return tecINSUFFICIENT_RESERVE; - } + if (auto const ret = + checkInsufficientReserve(view(), sleOwner, mPriorBalance, 1); + !isTesSuccess(ret)) + return ret; // Preclaim already verified that the Preauth entry does not yet exist. // Create and populate the Preauth entry. @@ -221,13 +218,10 @@ DepositPreauth::doApply() // A preauth counts against the reserve of the issuing account, but we // check the starting balance because we want to allow dipping into the // reserve to pay fees. - { - STAmount const reserve{view().fees().accountReserve( - sleOwner->getFieldU32(sfOwnerCount) + 1)}; - - if (mPriorBalance < reserve) - return tecINSUFFICIENT_RESERVE; - } + if (auto const ret = + checkInsufficientReserve(view(), sleOwner, mPriorBalance, 1); + !isTesSuccess(ret)) + return ret; // Preclaim already verified that the Preauth entry does not yet exist. // Create and populate the Preauth entry. diff --git a/src/xrpld/app/tx/detail/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp index 969fd4dd4c4..348abb2cb86 100644 --- a/src/xrpld/app/tx/detail/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -496,16 +496,17 @@ EscrowCreate::doApply() // Check reserve and funds availability STAmount const amount{ctx_.tx[sfAmount]}; - auto const reserve = - ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1); - - if (mSourceBalance < reserve) - return tecINSUFFICIENT_RESERVE; + if (auto const ret = + checkInsufficientReserve(ctx_.view(), sle, mSourceBalance, 1); + !isTesSuccess(ret)) + return ret; // Check reserve and funds availability if (isXRP(amount)) { - if (mSourceBalance < reserve + STAmount(amount).xrp()) + if (auto const ret = checkInsufficientReserve( + ctx_.view(), sle, mSourceBalance - STAmount(amount).xrp(), 1); + !isTesSuccess(ret)) return tecUNFUNDED; } @@ -974,11 +975,10 @@ escrowUnlockApplyHelper( if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && createAsset && !receiverIssuer) { - if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)}; - xrpBalance < view.fees().accountReserve(ownerCount + 1)) - { - return tecINSUFFICIENT_RESERVE; - } + if (auto const ret = + checkInsufficientReserve(view, sleDest, xrpBalance, 1); + !isTesSuccess(ret)) + return ret; if (auto const ter = MPTokenAuthorize::createMPToken(view, mptID, receiver, 0); diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp index eec41875739..8deb71921a8 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp @@ -106,10 +106,13 @@ MPTokenIssuanceCreate::create( if (!acct) return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE - if (args.priorBalance && - *(args.priorBalance) < - view.fees().accountReserve((*acct)[sfOwnerCount] + 1)) - return Unexpected(tecINSUFFICIENT_RESERVE); + if (args.priorBalance) + { + if (auto const ret = + checkInsufficientReserve(view, acct, *(args.priorBalance), 1); + !isTesSuccess(ret)) + return Unexpected(ret); // tecINSUFFICIENT_RESERVE + } auto const mptId = makeMptID(args.sequence, args.account); auto const mptIssuanceKeylet = keylet::mptIssuance(mptId); diff --git a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp index 3b4a27ffd7b..146afa0fd3c 100644 --- a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp +++ b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp @@ -463,10 +463,15 @@ NFTokenAcceptOffer::transferNFToken( auto const buyerOwnerCountAfter = sleBuyer->getFieldU32(sfOwnerCount); if (buyerOwnerCountAfter > buyerOwnerCountBefore) { - if (auto const reserve = - view().fees().accountReserve(buyerOwnerCountAfter); - buyerBalance < reserve) - return tecINSUFFICIENT_RESERVE; + if (auto const ret = checkInsufficientReserve( + ctx_.view(), + sleBuyer, + buyerBalance, + // Specify 0 here since ownerCount has already been + // incremented + 0); + !isTesSuccess(ret)) + return ret; } } diff --git a/src/xrpld/app/tx/detail/NFTokenMint.cpp b/src/xrpld/app/tx/detail/NFTokenMint.cpp index 8149d3b59de..24861c1a054 100644 --- a/src/xrpld/app/tx/detail/NFTokenMint.cpp +++ b/src/xrpld/app/tx/detail/NFTokenMint.cpp @@ -351,13 +351,19 @@ NFTokenMint::doApply() // allows NFTs to be added to the page (and burn fees) without // requiring the reserve to be met each time. The reserve is // only managed when a new NFT page or sell offer is added. - if (auto const ownerCountAfter = - view().read(keylet::account(account_))->getFieldU32(sfOwnerCount); + auto const sle = view().read(keylet::account(account_)); + if (auto const ownerCountAfter = sle->getFieldU32(sfOwnerCount); ownerCountAfter > ownerCountBefore) { - if (auto const reserve = view().fees().accountReserve(ownerCountAfter); - mPriorBalance < reserve) - return tecINSUFFICIENT_RESERVE; + if (auto const ret = checkInsufficientReserve( + ctx_.view(), + sle, + mPriorBalance, + // Specify 0 here since ownerCount has already been + // incremented + 0); + !isTesSuccess(ret)) + return ret; } return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/NFTokenUtils.cpp b/src/xrpld/app/tx/detail/NFTokenUtils.cpp index ad3e6f4d359..f9ffaf867aa 100644 --- a/src/xrpld/app/tx/detail/NFTokenUtils.cpp +++ b/src/xrpld/app/tx/detail/NFTokenUtils.cpp @@ -1033,9 +1033,10 @@ tokenOfferCreateApply( std::uint32_t txFlags) { Keylet const acctKeylet = keylet::account(acctID); - if (auto const acct = view.read(acctKeylet); - priorBalance < view.fees().accountReserve((*acct)[sfOwnerCount] + 1)) - return tecINSUFFICIENT_RESERVE; + if (auto const ret = checkInsufficientReserve( + view, view.read(acctKeylet), priorBalance, 1); + !isTesSuccess(ret)) + return ret; auto const offerID = keylet::nftoffer(acctID, seqProxy.value()); diff --git a/src/xrpld/app/tx/detail/PayChan.cpp b/src/xrpld/app/tx/detail/PayChan.cpp index 32c0abeb937..387dad06449 100644 --- a/src/xrpld/app/tx/detail/PayChan.cpp +++ b/src/xrpld/app/tx/detail/PayChan.cpp @@ -204,13 +204,14 @@ PayChanCreate::preclaim(PreclaimContext const& ctx) // Check reserve and funds availability { auto const balance = (*sle)[sfBalance]; - auto const reserve = - ctx.view.fees().accountReserve((*sle)[sfOwnerCount] + 1); - - if (balance < reserve) - return tecINSUFFICIENT_RESERVE; - - if (balance < reserve + ctx.tx[sfAmount]) + if (auto const ret = + checkInsufficientReserve(ctx.view, sle, balance, 1); + !isTesSuccess(ret)) + return ret; + + if (auto const ret = checkInsufficientReserve( + ctx.view, sle, balance - ctx.tx[sfAmount], 1); + !isTesSuccess(ret)) return tecUNFUNDED; } @@ -394,13 +395,14 @@ PayChanFund::doApply() { // Check reserve and funds availability auto const balance = (*sle)[sfBalance]; - auto const reserve = - ctx_.view().fees().accountReserve((*sle)[sfOwnerCount]); - - if (balance < reserve) - return tecINSUFFICIENT_RESERVE; - - if (balance < reserve + ctx_.tx[sfAmount]) + if (auto const ret = + checkInsufficientReserve(ctx_.view(), sle, balance, 0); + !isTesSuccess(ret)) + return ret; + + if (auto const ret = checkInsufficientReserve( + ctx_.view(), sle, balance - ctx_.tx[sfAmount], 0); + !isTesSuccess(ret)) return tecUNFUNDED; } diff --git a/src/xrpld/app/tx/detail/PermissionedDomainSet.cpp b/src/xrpld/app/tx/detail/PermissionedDomainSet.cpp index d9fa481bb6a..c14a39736fe 100644 --- a/src/xrpld/app/tx/detail/PermissionedDomainSet.cpp +++ b/src/xrpld/app/tx/detail/PermissionedDomainSet.cpp @@ -114,10 +114,10 @@ PermissionedDomainSet::doApply() // Create new permissioned domain. // Check reserve availability for new object creation auto const balance = STAmount((*ownerSle)[sfBalance]).xrp(); - auto const reserve = - ctx_.view().fees().accountReserve((*ownerSle)[sfOwnerCount] + 1); - if (balance < reserve) - return tecINSUFFICIENT_RESERVE; + if (auto const ret = + checkInsufficientReserve(ctx_.view(), ownerSle, balance, 1); + !isTesSuccess(ret)) + return ret; Keylet const pdKeylet = keylet::permissionedDomain( account_, ctx_.tx.getFieldU32(sfSequence)); diff --git a/src/xrpld/app/tx/detail/SetOracle.cpp b/src/xrpld/app/tx/detail/SetOracle.cpp index 81f20b4342b..448211add23 100644 --- a/src/xrpld/app/tx/detail/SetOracle.cpp +++ b/src/xrpld/app/tx/detail/SetOracle.cpp @@ -163,12 +163,12 @@ SetOracle::preclaim(PreclaimContext const& ctx) if (pairs.size() > maxOracleDataSeries) return tecARRAY_TOO_LARGE; - auto const reserve = ctx.view.fees().accountReserve( - sleSetter->getFieldU32(sfOwnerCount) + adjustReserve); auto const& balance = sleSetter->getFieldAmount(sfBalance); - if (balance < reserve) - return tecINSUFFICIENT_RESERVE; + if (auto const ret = checkInsufficientReserve( + ctx.view, sleSetter, balance, adjustReserve); + !isTesSuccess(ret)) + return ret; return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/SetSignerList.cpp b/src/xrpld/app/tx/detail/SetSignerList.cpp index b5d9d4d5b88..390636873e5 100644 --- a/src/xrpld/app/tx/detail/SetSignerList.cpp +++ b/src/xrpld/app/tx/detail/SetSignerList.cpp @@ -362,14 +362,13 @@ SetSignerList::replaceSignerList() flags = 0; } - XRPAmount const newReserve{ - view().fees().accountReserve(oldOwnerCount + addedOwnerCount)}; - // We check the reserve against the starting balance because we want to // allow dipping into the reserve to pay fees. This behavior is consistent // with CreateTicket. - if (mPriorBalance < newReserve) - return tecINSUFFICIENT_RESERVE; + if (auto const ret = checkInsufficientReserve( + ctx_.view(), sle, mPriorBalance, addedOwnerCount); + !isTesSuccess(ret)) + return ret; // Everything's ducky. Add the ltSIGNER_LIST to the ledger. auto signerList = std::make_shared(signerListKeylet); diff --git a/src/xrpld/app/tx/detail/VaultCreate.cpp b/src/xrpld/app/tx/detail/VaultCreate.cpp index 9447976a32b..b7251684e19 100644 --- a/src/xrpld/app/tx/detail/VaultCreate.cpp +++ b/src/xrpld/app/tx/detail/VaultCreate.cpp @@ -161,10 +161,13 @@ VaultCreate::doApply() if (auto ter = dirLink(view(), account_, vault)) return ter; + + if (auto const ret = + checkInsufficientReserve(view(), owner, mPriorBalance, 1); + !isTesSuccess(ret)) + return ret; + adjustOwnerCount(view(), owner, 1, j_); - auto ownerCount = owner->at(sfOwnerCount); - if (mPriorBalance < view().fees().accountReserve(ownerCount)) - return tecINSUFFICIENT_RESERVE; auto maybePseudo = createPseudoAccount(view(), vault->key(), sfVaultID); if (!maybePseudo) diff --git a/src/xrpld/app/tx/detail/XChainBridge.cpp b/src/xrpld/app/tx/detail/XChainBridge.cpp index 2587845df51..e8842e764df 100644 --- a/src/xrpld/app/tx/detail/XChainBridge.cpp +++ b/src/xrpld/app/tx/detail/XChainBridge.cpp @@ -1063,11 +1063,10 @@ applyCreateAccountAttestations( // Check reserve auto const balance = (*sleDoor)[sfBalance]; - auto const reserve = - psb.fees().accountReserve((*sleDoor)[sfOwnerCount] + 1); - - if (balance < reserve) - return Unexpected(tecINSUFFICIENT_RESERVE); + if (auto const ret = + checkInsufficientReserve(psb, sleDoor, balance, 1); + !isTesSuccess(ret)) + return Unexpected(ret); // tecINSUFFICIENT_RESERVE } std::vector atts; @@ -1483,11 +1482,10 @@ XChainCreateBridge::preclaim(PreclaimContext const& ctx) return terNO_ACCOUNT; auto const balance = (*sleAcc)[sfBalance]; - auto const reserve = - ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1); - - if (balance < reserve) - return tecINSUFFICIENT_RESERVE; + if (auto const ret = + checkInsufficientReserve(ctx.view, sleAcc, balance, 1); + !isTesSuccess(ret)) + return ret; } return tesSUCCESS; @@ -2018,11 +2016,10 @@ XChainCreateClaimID::preclaim(PreclaimContext const& ctx) return terNO_ACCOUNT; auto const balance = (*sleAcc)[sfBalance]; - auto const reserve = - ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1); - - if (balance < reserve) - return tecINSUFFICIENT_RESERVE; + if (auto const ret = + checkInsufficientReserve(ctx.view, sleAcc, balance, 1); + !isTesSuccess(ret)) + return ret; } return tesSUCCESS; From 839e34253684bf6841d9f1b7e0449088ab17f426 Mon Sep 17 00:00:00 2001 From: tequ Date: Sat, 4 Oct 2025 00:24:30 +0900 Subject: [PATCH 2/9] remove unused var --- src/xrpld/app/tx/detail/SetSignerList.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/xrpld/app/tx/detail/SetSignerList.cpp b/src/xrpld/app/tx/detail/SetSignerList.cpp index 390636873e5..13e74b76abc 100644 --- a/src/xrpld/app/tx/detail/SetSignerList.cpp +++ b/src/xrpld/app/tx/detail/SetSignerList.cpp @@ -350,8 +350,6 @@ SetSignerList::replaceSignerList() return tefINTERNAL; // Compute new reserve. Verify the account has funds to meet the reserve. - std::uint32_t const oldOwnerCount{(*sle)[sfOwnerCount]}; - // The required reserve changes based on featureMultiSignReserve... int addedOwnerCount{1}; std::uint32_t flags{lsfOneOwnerCount}; From 44f6c54a24ab2473322d977f910f2ad652a4b405 Mon Sep 17 00:00:00 2001 From: tequ Date: Sat, 4 Oct 2025 09:21:31 +0900 Subject: [PATCH 3/9] address reviews --- src/libxrpl/ledger/View.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index 170be487bd5..3291198fd35 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -1022,8 +1022,7 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal) uint32_t ownerCount(std::shared_ptr const& accountSle) { - auto const ownerCount = accountSle->getFieldU32(sfOwnerCount); - return ownerCount; + return accountSle->getFieldU32(sfOwnerCount); } TER @@ -1034,8 +1033,8 @@ checkInsufficientReserve( std::int32_t ownerCountDelta, std::int32_t accountCountDelta) { - STAmount const reserve{view.fees().accountReserve( - accSle->getFieldU32(sfOwnerCount) + ownerCountDelta)}; + STAmount const reserve{ + view.fees().accountReserve(ownerCount(accSle) + ownerCountDelta)}; if (accBalance < reserve) return tecINSUFFICIENT_RESERVE; From a1eeaa8702fa5584ba2e57a96b7040a33754c06b Mon Sep 17 00:00:00 2001 From: tequ Date: Sat, 4 Oct 2025 10:31:47 +0900 Subject: [PATCH 4/9] add `tecINSUFFICIENT_RESERVE` tests --- src/test/app/MPToken_test.cpp | 7 ++ src/test/app/PayChan_test.cpp | 33 +++++++ src/test/app/XChain_test.cpp | 104 +++++++++++++++-------- src/xrpld/app/tx/detail/PayChan.cpp | 4 +- src/xrpld/app/tx/detail/XChainBridge.cpp | 4 +- 5 files changed, 112 insertions(+), 40 deletions(-) diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index e9740e67dee..e33a47b60e2 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -156,6 +156,13 @@ class MPToken_test : public beast::unit_test::suite .metadata = "test", .err = temMALFORMED}); } + // test reserve check of MPTokenIssuanceCreate + { + Env env{*this, features}; + env.fund(env.current()->fees().accountReserve(1) - drops(1), alice); + MPTTester mptAlice(env, alice, {.fund = false}); + mptAlice.create({.err = tecINSUFFICIENT_RESERVE}); + } } void diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index fe9b70cf7f4..b74064e0654 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -2321,6 +2321,38 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(env.seq(bob) == bobSeq); } + void + testReserveCheck(FeatureBitset features) + { + testcase("Reserve check"); + using namespace jtx; + using namespace std::literals::chrono_literals; + Env env(*this, features); + + auto const acctReserveForOne = env.current()->fees().accountReserve(1); + + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(10000), bob); + env.close(); + + env.fund(acctReserveForOne - drops(1), alice); + env.close(); + + BEAST_EXPECT(env.balance(alice) < acctReserveForOne); + env(create(alice, bob, XRP(1), 100s, alice.pk()), + ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + env(pay(env.master, alice, XRP(1))); + env.close(); + + BEAST_EXPECT(env.balance(alice) > acctReserveForOne); + BEAST_EXPECT(env.balance(alice) < acctReserveForOne + XRP(1)); + env(create(alice, bob, XRP(1), 100s, alice.pk()), ter(tecUNFUNDED)); + env.close(); + } + void testWithFeats(FeatureBitset features) { @@ -2345,6 +2377,7 @@ struct PayChan_test : public beast::unit_test::suite testMetaAndOwnership(features); testAccountDelete(features); testUsingTickets(features); + testReserveCheck(features); } public: diff --git a/src/test/app/XChain_test.cpp b/src/test/app/XChain_test.cpp index 311ddda59be..17bbdfbd374 100644 --- a/src/test/app/XChain_test.cpp +++ b/src/test/app/XChain_test.cpp @@ -2614,18 +2614,9 @@ struct XChain_test : public beast::unit_test::suite, testcase("Add Non Batch Account Create Attestation"); - XEnv mcEnv(*this); - XEnv scEnv(*this, true); - - XRPAmount tx_fee = mcEnv.txFee(); - Account a{"a"}; Account doorA{"doorA"}; - STAmount funds{XRP(10000)}; - mcEnv.fund(funds, a); - mcEnv.fund(funds, doorA); - Account ua{"ua"}; // unfunded account we want to create BridgeDef xrp_b{ @@ -2639,47 +2630,88 @@ struct XChain_test : public beast::unit_test::suite, signers, Json::nullValue}; - xrp_b.initBridge(mcEnv, scEnv); - - auto const amt = XRP(777); - auto const amt_plus_reward = amt + xrp_b.reward; { - Balance bal_doorA(mcEnv, doorA); - Balance bal_a(mcEnv, a); + XEnv mcEnv(*this); + XEnv scEnv(*this, true); - mcEnv - .tx(sidechain_xchain_account_create( - a, xrp_b.jvb, ua, amt, xrp_b.reward)) - .close(); + XRPAmount tx_fee = mcEnv.txFee(); - BEAST_EXPECT(bal_doorA.diff() == amt_plus_reward); - BEAST_EXPECT(bal_a.diff() == -(amt_plus_reward + tx_fee)); - } + STAmount funds{XRP(10000)}; + mcEnv.fund(funds, a); + mcEnv.fund(funds, doorA); + + xrp_b.initBridge(mcEnv, scEnv); + + auto const amt = XRP(777); + auto const amt_plus_reward = amt + xrp_b.reward; + { + Balance bal_doorA(mcEnv, doorA); + Balance bal_a(mcEnv, a); + + mcEnv + .tx(sidechain_xchain_account_create( + a, xrp_b.jvb, ua, amt, xrp_b.reward)) + .close(); - for (int i = 0; i < signers.size(); ++i) + BEAST_EXPECT(bal_doorA.diff() == amt_plus_reward); + BEAST_EXPECT(bal_a.diff() == -(amt_plus_reward + tx_fee)); + } + + for (int i = 0; i < signers.size(); ++i) + { + auto const att = create_account_attestation( + signers[0].account, + xrp_b.jvb, + a, + amt, + xrp_b.reward, + signers[i].account, + true, + 1, + ua, + signers[i]); + TER const expectedTER = i < xrp_b.quorum + ? tesSUCCESS + : TER{tecXCHAIN_ACCOUNT_CREATE_PAST}; + + scEnv.tx(att, ter(expectedTER)).close(); + if (i + 1 < xrp_b.quorum) + BEAST_EXPECT(!scEnv.env_.le(ua)); + else + BEAST_EXPECT(scEnv.env_.le(ua)); + } + BEAST_EXPECT(scEnv.env_.le(ua)); + } { + XEnv mcEnv(*this); + XEnv scEnv(*this, true); + + STAmount funds{XRP(10000)}; + mcEnv.fund(funds, a); + auto const mcBaseFee = mcEnv.env_.current()->fees().base; + mcEnv.fund( + // for SignerList, Bridge: 2 increments + // for SetSignerList, BridgeCreate: 2 base fees + mcEnv.env_.current()->fees().accountReserve(3) + mcBaseFee * 2 - + drops(1), + doorA); + + xrp_b.initBridge(mcEnv, scEnv); + + auto const amt = XRP(777); auto const att = create_account_attestation( signers[0].account, xrp_b.jvb, a, amt, xrp_b.reward, - signers[i].account, - true, + signers[0].account, + false, 1, ua, - signers[i]); - TER const expectedTER = i < xrp_b.quorum - ? tesSUCCESS - : TER{tecXCHAIN_ACCOUNT_CREATE_PAST}; - - scEnv.tx(att, ter(expectedTER)).close(); - if (i + 1 < xrp_b.quorum) - BEAST_EXPECT(!scEnv.env_.le(ua)); - else - BEAST_EXPECT(scEnv.env_.le(ua)); + signers[0]); + mcEnv.tx(att, ter(tecINSUFFICIENT_RESERVE)).close(); } - BEAST_EXPECT(scEnv.env_.le(ua)); } void diff --git a/src/xrpld/app/tx/detail/PayChan.cpp b/src/xrpld/app/tx/detail/PayChan.cpp index 387dad06449..4582e951fae 100644 --- a/src/xrpld/app/tx/detail/PayChan.cpp +++ b/src/xrpld/app/tx/detail/PayChan.cpp @@ -398,12 +398,12 @@ PayChanFund::doApply() if (auto const ret = checkInsufficientReserve(ctx_.view(), sle, balance, 0); !isTesSuccess(ret)) - return ret; + return ret; // LCOV_EXCL_LINE if (auto const ret = checkInsufficientReserve( ctx_.view(), sle, balance - ctx_.tx[sfAmount], 0); !isTesSuccess(ret)) - return tecUNFUNDED; + return tecUNFUNDED; // LCOV_EXCL_LINE } // do not allow adding funds if dst does not exist diff --git a/src/xrpld/app/tx/detail/XChainBridge.cpp b/src/xrpld/app/tx/detail/XChainBridge.cpp index e8842e764df..cd10a4e2aeb 100644 --- a/src/xrpld/app/tx/detail/XChainBridge.cpp +++ b/src/xrpld/app/tx/detail/XChainBridge.cpp @@ -1479,7 +1479,7 @@ XChainCreateBridge::preclaim(PreclaimContext const& ctx) // Check reserve auto const sleAcc = ctx.view.read(keylet::account(account)); if (!sleAcc) - return terNO_ACCOUNT; + return terNO_ACCOUNT; // LCOV_EXCL_LINE auto const balance = (*sleAcc)[sfBalance]; if (auto const ret = @@ -2013,7 +2013,7 @@ XChainCreateClaimID::preclaim(PreclaimContext const& ctx) // Check reserve auto const sleAcc = ctx.view.read(keylet::account(account)); if (!sleAcc) - return terNO_ACCOUNT; + return terNO_ACCOUNT; // LCOV_EXCL_LINE auto const balance = (*sleAcc)[sfBalance]; if (auto const ret = From 48b273a66a840f7c7cec19169ace7e41586c53fc Mon Sep 17 00:00:00 2001 From: tequ Date: Mon, 6 Oct 2025 20:49:37 +0900 Subject: [PATCH 5/9] address reviews --- src/test/app/PayChan_test.cpp | 2 +- src/xrpld/app/tx/detail/PayChan.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index b74064e0654..1ece4986eb3 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -2339,7 +2339,7 @@ struct PayChan_test : public beast::unit_test::suite env.fund(acctReserveForOne - drops(1), alice); env.close(); - BEAST_EXPECT(env.balance(alice) < acctReserveForOne); + BEAST_EXPECT(env.balance(alice) == acctReserveForOne - drops(1)); env(create(alice, bob, XRP(1), 100s, alice.pk()), ter(tecINSUFFICIENT_RESERVE)); env.close(); diff --git a/src/xrpld/app/tx/detail/PayChan.cpp b/src/xrpld/app/tx/detail/PayChan.cpp index 4582e951fae..387dad06449 100644 --- a/src/xrpld/app/tx/detail/PayChan.cpp +++ b/src/xrpld/app/tx/detail/PayChan.cpp @@ -398,12 +398,12 @@ PayChanFund::doApply() if (auto const ret = checkInsufficientReserve(ctx_.view(), sle, balance, 0); !isTesSuccess(ret)) - return ret; // LCOV_EXCL_LINE + return ret; if (auto const ret = checkInsufficientReserve( ctx_.view(), sle, balance - ctx_.tx[sfAmount], 0); !isTesSuccess(ret)) - return tecUNFUNDED; // LCOV_EXCL_LINE + return tecUNFUNDED; } // do not allow adding funds if dst does not exist From 498cb3022805973e69e29ba1ad6e351f78d03e97 Mon Sep 17 00:00:00 2001 From: tequ Date: Wed, 8 Oct 2025 16:48:47 +0900 Subject: [PATCH 6/9] additional refactoring for `tecINSUF_RESERVE_LINE` --- src/xrpld/app/tx/detail/AMMCreate.cpp | 7 +++++-- src/xrpld/app/tx/detail/AMMDeposit.cpp | 14 +++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/xrpld/app/tx/detail/AMMCreate.cpp b/src/xrpld/app/tx/detail/AMMCreate.cpp index 63e20b42fbf..cef9c8f3044 100644 --- a/src/xrpld/app/tx/detail/AMMCreate.cpp +++ b/src/xrpld/app/tx/detail/AMMCreate.cpp @@ -136,14 +136,17 @@ AMMCreate::preclaim(PreclaimContext const& ctx) } // Check the reserve for LPToken trustline - STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j); // Insufficient reserve - if (xrpBalance <= beast::zero) + auto const accountSle = ctx.view.read(keylet::account(accountID)); + if (auto const ret = checkInsufficientReserve( + ctx.view, accountSle, accountSle->getFieldAmount(sfBalance), 1); + !isTesSuccess(ret)) { JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves"; return tecINSUF_RESERVE_LINE; } + STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j); auto insufficientBalance = [&](STAmount const& asset) { if (isXRP(asset)) return xrpBalance < asset; diff --git a/src/xrpld/app/tx/detail/AMMDeposit.cpp b/src/xrpld/app/tx/detail/AMMDeposit.cpp index 8a3e50ed634..e997fa2dd43 100644 --- a/src/xrpld/app/tx/detail/AMMDeposit.cpp +++ b/src/xrpld/app/tx/detail/AMMDeposit.cpp @@ -226,7 +226,13 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) // Adjust the reserve if LP doesn't have LPToken trustline auto const sle = ctx.view.read( keylet::line(accountID, lpIssue.account, lpIssue.currency)); - if (xrpLiquid(ctx.view, accountID, !sle, ctx.j) >= deposit) + auto const accountSle = ctx.view.read(keylet::account(accountID)); + if (auto const ret = checkInsufficientReserve( + ctx.view, + accountSle, + accountSle->getFieldAmount(sfBalance) - deposit, + !sle); + isTesSuccess(ret)) return TER(tesSUCCESS); if (sle) return tecUNFUNDED_AMM; @@ -354,9 +360,11 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) // We checked above but need to check again if depositing IOU only. if (ammLPHolds(ctx.view, *ammSle, accountID, ctx.j) == beast::zero) { - STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j); + auto const accountSle = ctx.view.read(keylet::account(accountID)); // Insufficient reserve - if (xrpBalance <= beast::zero) + if (auto const ret = checkInsufficientReserve( + ctx.view, accountSle, accountSle->getFieldAmount(sfBalance), 1); + !isTesSuccess(ret)) { JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves"; return tecINSUF_RESERVE_LINE; From 27e0ee67e331f292a9b338f1b946567d3e7d24d5 Mon Sep 17 00:00:00 2001 From: tequ Date: Mon, 27 Oct 2025 16:21:19 +0900 Subject: [PATCH 7/9] fix insuff check to use helper in addEmptyHolding() --- src/libxrpl/ledger/View.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index c8ed07edb25..2586fa94b38 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -1267,8 +1267,9 @@ addEmptyHolding( return tecDUPLICATE; // Can the account cover the trust line reserve ? - std::uint32_t const ownerCount = sleDst->at(sfOwnerCount); - if (priorBalance < view.fees().accountReserve(ownerCount + 1)) + if (auto const ret = + checkInsufficientReserve(view, sleDst, priorBalance, 1); + !isTesSuccess(ret)) return tecNO_LINE_INSUF_RESERVE; return trustCreate( From ba69dcd2bfe550d66284e47a36349d8f64be554b Mon Sep 17 00:00:00 2001 From: tequ Date: Fri, 7 Nov 2025 17:30:38 +0900 Subject: [PATCH 8/9] remove unused arg --- include/xrpl/ledger/View.h | 3 +-- src/libxrpl/ledger/View.cpp | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index 4ff9e8aa70e..1b3e8cf79b6 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -440,8 +440,7 @@ checkInsufficientReserve( ReadView const& view, std::shared_ptr const& accSle, STAmount const& accBalance, - std::int32_t ownerCountDelta, - std::int32_t accountCountDelta = 0); + std::int32_t ownerCountDelta); //------------------------------------------------------------------------------ // diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index f4331eca0cb..070db522928 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -1014,8 +1014,7 @@ checkInsufficientReserve( ReadView const& view, std::shared_ptr const& accSle, STAmount const& accBalance, - std::int32_t ownerCountDelta, - std::int32_t accountCountDelta) + std::int32_t ownerCountDelta) { STAmount const reserve{ view.fees().accountReserve(ownerCount(accSle) + ownerCountDelta)}; From d024dab4ce5ebf7c4a7fe63937aba420afb966b5 Mon Sep 17 00:00:00 2001 From: tequ Date: Fri, 19 Dec 2025 00:36:53 +0900 Subject: [PATCH 9/9] additional refactor for `tecNO_LINE_INSUF_RESERVE` --- src/xrpld/app/tx/detail/CashCheck.cpp | 5 +++-- src/xrpld/app/tx/detail/Escrow.cpp | 5 +++-- src/xrpld/app/tx/detail/LoanBrokerSet.cpp | 5 +++-- src/xrpld/app/tx/detail/LoanSet.cpp | 3 ++- src/xrpld/app/tx/detail/SetTrust.cpp | 17 +++++++++++------ 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/xrpld/app/tx/detail/CashCheck.cpp b/src/xrpld/app/tx/detail/CashCheck.cpp index c32b7dfe4e8..8888a4be62b 100644 --- a/src/xrpld/app/tx/detail/CashCheck.cpp +++ b/src/xrpld/app/tx/detail/CashCheck.cpp @@ -329,8 +329,9 @@ CashCheck::doApply() auto const sleDst = psb.peek(keylet::account(account_)); // Can the account cover the trust line's reserve? - if (std::uint32_t const ownerCount = {sleDst->at(sfOwnerCount)}; - mPriorBalance < psb.fees().accountReserve(ownerCount + 1)) + if (auto const ret = + checkInsufficientReserve(psb, sleDst, mPriorBalance, 1); + !isTesSuccess(ret)) { JLOG(j_.trace()) << "Trust line does not exist. " "Insufficent reserve to create line."; diff --git a/src/xrpld/app/tx/detail/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp index d955d8d3953..a08d4cfac30 100644 --- a/src/xrpld/app/tx/detail/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -771,8 +771,9 @@ escrowUnlockApplyHelper( if (!view.exists(trustLineKey) && createAsset && !receiverIssuer) { // Can the account cover the trust line's reserve? - if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)}; - xrpBalance < view.fees().accountReserve(ownerCount + 1)) + if (auto const ret = + checkInsufficientReserve(view, sleDest, xrpBalance, 1); + !isTesSuccess(ret)) { JLOG(journal.trace()) << "Trust line does not exist. " "Insufficent reserve to create line."; diff --git a/src/xrpld/app/tx/detail/LoanBrokerSet.cpp b/src/xrpld/app/tx/detail/LoanBrokerSet.cpp index dd471487e15..bc4db8e9109 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerSet.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerSet.cpp @@ -170,8 +170,9 @@ LoanBrokerSet::doApply() // Increases the owner count by two: one for the LoanBroker object, and // one for the pseudo-account. - if (auto const ret = checkInsufficientReserve(view, owner, mPriorBalance, 2); - !isTesSuccess(ret)) + if (auto const ret = + checkInsufficientReserve(view, owner, mPriorBalance, 2); + !isTesSuccess(ret)) return ret; adjustOwnerCount(view, owner, 2, j_); diff --git a/src/xrpld/app/tx/detail/LoanSet.cpp b/src/xrpld/app/tx/detail/LoanSet.cpp index 14c538cc5dd..1be6fafd55e 100644 --- a/src/xrpld/app/tx/detail/LoanSet.cpp +++ b/src/xrpld/app/tx/detail/LoanSet.cpp @@ -482,7 +482,8 @@ LoanSet::doApply() auto const balance = account_ == borrower ? mPriorBalance : borrowerSle->at(sfBalance).value().xrp(); - if (auto const ret = checkInsufficientReserve(view, borrowerSle, balance, 1); + if (auto const ret = + checkInsufficientReserve(view, borrowerSle, balance, 1); !isTesSuccess(ret)) return ret; } diff --git a/src/xrpld/app/tx/detail/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp index 77d7e6d9b5d..cb3e9159613 100644 --- a/src/xrpld/app/tx/detail/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -355,9 +355,7 @@ SetTrust::doApply() // well. A person with no intention of using the gateway // could use the extra XRP for their own purposes. - XRPAmount const reserveCreate( - (uOwnerCount < 2) ? XRPAmount(beast::zero) - : view().fees().accountReserve(uOwnerCount + 1)); + bool const freeTrustLine = uOwnerCount < 2; std::uint32_t uQualityIn(bQualityIn ? ctx_.tx.getFieldU32(sfQualityIn) : 0); std::uint32_t uQualityOut( @@ -616,7 +614,9 @@ SetTrust::doApply() view(), sleRippleState, uLowAccountID, uHighAccountID, viewJ); } // Reserve is not scaled by load. - else if (bReserveIncrease && mPriorBalance < reserveCreate) + else if (auto const ret = + checkInsufficientReserve(view(), sle, mPriorBalance, 0); + !freeTrustLine && bReserveIncrease && !isTesSuccess(ret)) { JLOG(j_.trace()) << "Delay transaction: Insufficent reserve to " "add trust line."; @@ -645,8 +645,13 @@ SetTrust::doApply() << "Redundant: Setting non-existent ripple line to defaults."; return tecNO_LINE_REDUNDANT; } - else if (mPriorBalance < reserveCreate) // Reserve is not scaled by - // load. + else if (auto const ret = checkInsufficientReserve( + view(), + sle, + mPriorBalance, + 0); + !freeTrustLine && + !isTesSuccess(ret)) // Reserve is not scaled by load. { JLOG(j_.trace()) << "Delay transaction: Line does not exist. " "Insufficent reserve to create line.";