diff --git a/include/xrpl/protocol/Fees.h b/include/xrpl/protocol/Fees.h index 4393f1a1d9..937915c8f6 100644 --- a/include/xrpl/protocol/Fees.h +++ b/include/xrpl/protocol/Fees.h @@ -46,9 +46,18 @@ struct Fees the reserve increment times the number of increments. */ XRPAmount - accountReserve(std::size_t ownerCount) const + accountReserve( + std::size_t ownerCount, + std::size_t sponsoredOwnerCount = 0, + std::size_t sponsoringOwnerCount = 0, + bool isAccountSponsored = false, + std::size_t sponsoringAccountCount = 0) const { - return reserve + ownerCount * increment; + // return reserve + ownerCount * increment; + return (isAccountSponsored ? XRPAmount(0) : reserve) + + increment * + (ownerCount + sponsoringOwnerCount - sponsoredOwnerCount) + + reserve * sponsoringAccountCount; } }; diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index 36d5585010..55d2a0137c 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -99,7 +100,20 @@ public: Account const bob("bob"); Account const sponsor("sponsor"); - env.fund(XRP(10000), alice, bob, sponsor); + auto const reserve = env.current()->fees().reserve; + auto const increment = env.current()->fees().increment; + + env.fund(XRP(10000), alice, bob); + env.fund(drops(reserve) + drops(increment) - drops(1), sponsor); + + // check sponsor balance + env(check::create(alice, bob, XRP(1)), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor), + ter(tecINSUFFICIENT_RESERVE)); + + env(pay(alice, sponsor, drops(1))); + env.close(); // CheckCreate auto const seq = env.seq(alice); diff --git a/src/xrpld/app/tx/detail/AMMClawback.cpp b/src/xrpld/app/tx/detail/AMMClawback.cpp index 07c5151727..4d93b8c2a7 100644 --- a/src/xrpld/app/tx/detail/AMMClawback.cpp +++ b/src/xrpld/app/tx/detail/AMMClawback.cpp @@ -192,6 +192,7 @@ AMMClawback::applyGuts(Sandbox& sb) std::tie(result, newLPTokenBalance, amountWithdraw, amount2Withdraw) = AMMWithdraw::equalWithdrawTokens( sb, + ctx_.tx, *ammSle, holder, ammAccount, @@ -273,6 +274,7 @@ AMMClawback::equalWithdrawMatchingOneAmount( // tfee is actually not used, so pass tfee as 0. return AMMWithdraw::equalWithdrawTokens( sb, + ctx_.tx, ammSle, holder, ammAccount, @@ -308,6 +310,7 @@ AMMClawback::equalWithdrawMatchingOneAmount( return AMMWithdraw::withdraw( sb, + ctx_.tx, ammSle, ammAccount, holder, @@ -327,6 +330,7 @@ AMMClawback::equalWithdrawMatchingOneAmount( // tfee is actually not used, so pass tfee as 0. return AMMWithdraw::withdraw( sb, + ctx_.tx, ammSle, ammAccount, holder, diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.cpp b/src/xrpld/app/tx/detail/AMMWithdraw.cpp index 2ad1a19df5..abba36afee 100644 --- a/src/xrpld/app/tx/detail/AMMWithdraw.cpp +++ b/src/xrpld/app/tx/detail/AMMWithdraw.cpp @@ -339,6 +339,7 @@ AMMWithdraw::applyGuts(Sandbox& sb) if (subTxType & tfTwoAsset) return equalWithdrawLimit( sb, + ctx_.tx, *ammSle, ammAccountID, amountBalance, @@ -350,6 +351,7 @@ AMMWithdraw::applyGuts(Sandbox& sb) if (subTxType & tfOneAssetLPToken || subTxType & tfOneAssetWithdrawAll) return singleWithdrawTokens( sb, + ctx_.tx, *ammSle, ammAccountID, amountBalance, @@ -360,6 +362,7 @@ AMMWithdraw::applyGuts(Sandbox& sb) if (subTxType & tfLimitLPToken) return singleWithdrawEPrice( sb, + ctx_.tx, *ammSle, ammAccountID, amountBalance, @@ -370,6 +373,7 @@ AMMWithdraw::applyGuts(Sandbox& sb) if (subTxType & tfSingleAsset) return singleWithdraw( sb, + ctx_.tx, *ammSle, ammAccountID, amountBalance, @@ -380,6 +384,7 @@ AMMWithdraw::applyGuts(Sandbox& sb) { return equalWithdrawTokens( sb, + ctx_.tx, *ammSle, ammAccountID, amountBalance, @@ -435,6 +440,7 @@ AMMWithdraw::doApply() std::pair AMMWithdraw::withdraw( Sandbox& view, + STTx const& tx, SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, @@ -448,6 +454,7 @@ AMMWithdraw::withdraw( STAmount newLPTokenBalance; std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = withdraw( view, + tx, ammSle, ammAccount, account_, @@ -467,6 +474,7 @@ AMMWithdraw::withdraw( std::tuple> AMMWithdraw::withdraw( Sandbox& view, + STTx const& tx, SLE const& ammSle, AccountID const& ammAccount, AccountID const& account, @@ -591,13 +599,18 @@ AMMWithdraw::withdraw( 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; + if (ownerCount >= 2) + { + auto const sponsor = getTxReserveSponsor(view, tx); + if (auto const ret = checkInsufficientReserve( + view, + sleAccount, + std::max(priorBalance, balance), + sponsor, + 1); + !isTesSuccess(ret)) + return ret; + } } return tesSUCCESS; }; @@ -685,6 +698,7 @@ adjustLPTokensIn( std::pair AMMWithdraw::equalWithdrawTokens( Sandbox& view, + STTx const& tx, SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, @@ -699,6 +713,7 @@ AMMWithdraw::equalWithdrawTokens( std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = equalWithdrawTokens( view, + tx, ammSle, account_, ammAccount, @@ -749,6 +764,7 @@ AMMWithdraw::deleteAMMAccountIfEmpty( std::tuple> AMMWithdraw::equalWithdrawTokens( Sandbox& view, + STTx const& tx, SLE const& ammSle, AccountID const account, AccountID const& ammAccount, @@ -770,6 +786,7 @@ AMMWithdraw::equalWithdrawTokens( { return withdraw( view, + tx, ammSle, ammAccount, account, @@ -805,6 +822,7 @@ AMMWithdraw::equalWithdrawTokens( return withdraw( view, + tx, ammSle, ammAccount, account, @@ -857,6 +875,7 @@ AMMWithdraw::equalWithdrawTokens( std::pair AMMWithdraw::equalWithdrawLimit( Sandbox& view, + STTx const& tx, SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, @@ -881,6 +900,7 @@ AMMWithdraw::equalWithdrawLimit( { return withdraw( view, + tx, ammSle, ammAccount, amountBalance, @@ -914,6 +934,7 @@ AMMWithdraw::equalWithdrawLimit( return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE return withdraw( view, + tx, ammSle, ammAccount, amountBalance, @@ -932,6 +953,7 @@ AMMWithdraw::equalWithdrawLimit( std::pair AMMWithdraw::singleWithdraw( Sandbox& view, + STTx const& tx, SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, @@ -958,6 +980,7 @@ AMMWithdraw::singleWithdraw( return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE return withdraw( view, + tx, ammSle, ammAccount, amountBalance, @@ -981,6 +1004,7 @@ AMMWithdraw::singleWithdraw( std::pair AMMWithdraw::singleWithdrawTokens( Sandbox& view, + STTx const& tx, SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, @@ -1000,6 +1024,7 @@ AMMWithdraw::singleWithdrawTokens( { return withdraw( view, + tx, ammSle, ammAccount, amountBalance, @@ -1035,6 +1060,7 @@ AMMWithdraw::singleWithdrawTokens( std::pair AMMWithdraw::singleWithdrawEPrice( Sandbox& view, + STTx const& tx, SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, @@ -1080,6 +1106,7 @@ AMMWithdraw::singleWithdrawEPrice( { return withdraw( view, + tx, ammSle, ammAccount, amountBalance, diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.h b/src/xrpld/app/tx/detail/AMMWithdraw.h index 1de91fd787..6cc0811f14 100644 --- a/src/xrpld/app/tx/detail/AMMWithdraw.h +++ b/src/xrpld/app/tx/detail/AMMWithdraw.h @@ -102,6 +102,7 @@ public: static std::tuple> equalWithdrawTokens( Sandbox& view, + STTx const& tx, SLE const& ammSle, AccountID const account, AccountID const& ammAccount, @@ -135,6 +136,7 @@ public: static std::tuple> withdraw( Sandbox& view, + STTx const& tx, SLE const& ammSle, AccountID const& ammAccount, AccountID const& account, @@ -177,6 +179,7 @@ private: std::pair withdraw( Sandbox& view, + STTx const& tx, SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, @@ -202,6 +205,7 @@ private: std::pair equalWithdrawTokens( Sandbox& view, + STTx const& tx, SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, @@ -227,6 +231,7 @@ private: std::pair equalWithdrawLimit( Sandbox& view, + STTx const& tx, SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, @@ -249,6 +254,7 @@ private: std::pair singleWithdraw( Sandbox& view, + STTx const& tx, SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, @@ -270,6 +276,7 @@ private: std::pair singleWithdrawTokens( Sandbox& view, + STTx const& tx, SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, @@ -292,6 +299,7 @@ private: std::pair singleWithdrawEPrice( Sandbox& view, + STTx const& tx, SLE const& ammSle, AccountID const& ammAccount, STAmount const& amountBalance, diff --git a/src/xrpld/app/tx/detail/CashCheck.cpp b/src/xrpld/app/tx/detail/CashCheck.cpp index c2c3f37259..c31591542b 100644 --- a/src/xrpld/app/tx/detail/CashCheck.cpp +++ b/src/xrpld/app/tx/detail/CashCheck.cpp @@ -368,8 +368,10 @@ 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)) + auto const sponsor = getTxReserveSponsor(psb, ctx_.tx); + if (auto const ret = checkInsufficientReserve( + psb, sleDst, mPriorBalance, sponsor, 1); + !isTesSuccess(ret)) { JLOG(j_.trace()) << "Trust line does not exist. " "Insufficent reserve to create line."; diff --git a/src/xrpld/app/tx/detail/CreateCheck.cpp b/src/xrpld/app/tx/detail/CreateCheck.cpp index fbc084fd1c..a808698856 100644 --- a/src/xrpld/app/tx/detail/CreateCheck.cpp +++ b/src/xrpld/app/tx/detail/CreateCheck.cpp @@ -177,13 +177,11 @@ 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; - } + auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); + if (auto const ret = + checkInsufficientReserve(view(), sle, mPriorBalance, sponsor, 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. @@ -243,7 +241,6 @@ CreateCheck::doApply() sleCheck->setFieldU64(sfOwnerNode, *page); } // If we succeeded, the new entry counts against the creator's reserve. - auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); adjustOwnerCount(view(), sle, sponsor, 1, viewJ); addSponsorToLedgerEntry(sleCheck, sponsor); return tesSUCCESS; diff --git a/src/xrpld/app/tx/detail/CreateOffer.cpp b/src/xrpld/app/tx/detail/CreateOffer.cpp index fc1bcda26b..ffbcdb0afe 100644 --- a/src/xrpld/app/tx/detail/CreateOffer.cpp +++ b/src/xrpld/app/tx/detail/CreateOffer.cpp @@ -813,10 +813,10 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel) return {tefINTERNAL, false}; { - XRPAmount reserve = - sb.fees().accountReserve(sleCreator->getFieldU32(sfOwnerCount) + 1); - - if (mPriorBalance < reserve) + auto const sponsor = getTxReserveSponsor(sb, ctx_.tx); + if (auto const ret = checkInsufficientReserve( + sb, sleCreator, mPriorBalance, sponsor, 1); + !isTesSuccess(ret)) { // If we are here, the signing account had an insufficient reserve // *prior* to our processing. If something actually crossed, then diff --git a/src/xrpld/app/tx/detail/CreateTicket.cpp b/src/xrpld/app/tx/detail/CreateTicket.cpp index 00c5a6c7b8..e2b97cecd4 100644 --- a/src/xrpld/app/tx/detail/CreateTicket.cpp +++ b/src/xrpld/app/tx/detail/CreateTicket.cpp @@ -91,13 +91,11 @@ 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; - } + auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); + if (auto const ret = checkInsufficientReserve( + view(), sleAccountRoot, mPriorBalance, sponsor, ticketCount); + !isTesSuccess(ret)) + return ret; beast::Journal viewJ{ctx_.app.journal("View")}; @@ -113,7 +111,6 @@ CreateTicket::doApply() txSeq != 0 && txSeq != (firstTicketSeq - 1)) return tefINTERNAL; - auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); for (std::uint32_t i = 0; i < ticketCount; ++i) { std::uint32_t const curTicketSeq = firstTicketSeq + i; diff --git a/src/xrpld/app/tx/detail/Credentials.cpp b/src/xrpld/app/tx/detail/Credentials.cpp index 36e4ebbc73..48f6bff2a7 100644 --- a/src/xrpld/app/tx/detail/Credentials.cpp +++ b/src/xrpld/app/tx/detail/Credentials.cpp @@ -148,12 +148,11 @@ CredentialCreate::doApply() if (!sleIssuer) return tefINTERNAL; - { - STAmount const reserve{view().fees().accountReserve( - sleIssuer->getFieldU32(sfOwnerCount) + 1)}; - if (mPriorBalance < reserve) - return tecINSUFFICIENT_RESERVE; - } + auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); + if (auto const ret = checkInsufficientReserve( + view(), sleIssuer, mPriorBalance, sponsor, 1); + !isTesSuccess(ret)) + return ret; sleCred->setAccountID(sfSubject, subject); sleCred->setAccountID(sfIssuer, account_); @@ -174,7 +173,6 @@ CredentialCreate::doApply() return tecDIR_FULL; sleCred->setFieldU64(sfIssuerNode, *page); - auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); adjustOwnerCount(view(), sleIssuer, sponsor, 1, j_); addSponsorToLedgerEntry(sleCred, sponsor); } @@ -372,12 +370,11 @@ CredentialAccept::doApply() if (!sleSubject || !sleIssuer) return tefINTERNAL; - { - STAmount const reserve{view().fees().accountReserve( - sleSubject->getFieldU32(sfOwnerCount) + 1)}; - if (mPriorBalance < reserve) - return tecINSUFFICIENT_RESERVE; - } + auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); + if (auto const ret = checkInsufficientReserve( + view(), sleSubject, mPriorBalance, sponsor, 1); + !isTesSuccess(ret)) + return ret; auto const credType(ctx_.tx[sfCredentialType]); Keylet const credentialKey = keylet::credential(account_, issuer, credType); @@ -394,8 +391,7 @@ CredentialAccept::doApply() sleCred->setFieldU32(sfFlags, lsfAccepted); view().update(sleCred); - auto const currentSponsor = getTxReserveSponsor(view(), ctx_.tx); - adjustOwnerCount(view(), sleIssuer, currentSponsor, -1, j_); + adjustOwnerCount(view(), sleIssuer, sponsor, -1, j_); auto const newSponsor = getTxReserveSponsor(view(), ctx_.tx); adjustOwnerCount(view(), sleSubject, newSponsor, 1, j_); addSponsorToLedgerEntry(sleCred, newSponsor); diff --git a/src/xrpld/app/tx/detail/DID.cpp b/src/xrpld/app/tx/detail/DID.cpp index f8dd9619d8..37d73327dd 100644 --- a/src/xrpld/app/tx/detail/DID.cpp +++ b/src/xrpld/app/tx/detail/DID.cpp @@ -88,14 +88,12 @@ 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 sponsor = getTxReserveSponsor(ctx.view(), ctx.tx); + auto const balance = STAmount((*sleAccount)[sfBalance]).xrp(); + if (auto const ret = checkInsufficientReserve( + ctx.view(), sleAccount, balance, sponsor, 1); + !isTesSuccess(ret)) + return ret; // Add ledger object to ledger ctx.view().insert(sle); @@ -108,7 +106,6 @@ addSLE( return tecDIR_FULL; (*sle)[sfOwnerNode] = *page; } - auto const sponsor = getTxReserveSponsor(ctx.view(), ctx.tx); adjustOwnerCount(ctx.view(), sleAccount, sponsor, 1, ctx.journal); addSponsorToLedgerEntry(sle, sponsor); ctx.view().update(sleAccount); diff --git a/src/xrpld/app/tx/detail/DelegateSet.cpp b/src/xrpld/app/tx/detail/DelegateSet.cpp index a98e3d5951..46b6425c23 100644 --- a/src/xrpld/app/tx/detail/DelegateSet.cpp +++ b/src/xrpld/app/tx/detail/DelegateSet.cpp @@ -99,11 +99,11 @@ DelegateSet::doApply() return tesSUCCESS; } - STAmount const reserve{ctx_.view().fees().accountReserve( - sleOwner->getFieldU32(sfOwnerCount) + 1)}; - - if (mPriorBalance < reserve) - return tecINSUFFICIENT_RESERVE; + auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); + if (auto const ret = checkInsufficientReserve( + view(), sleOwner, mPriorBalance, sponsor, 1); + !isTesSuccess(ret)) + return ret; auto const& permissions = ctx_.tx.getFieldArray(sfPermissions); if (!permissions.empty()) @@ -123,7 +123,6 @@ DelegateSet::doApply() (*sle)[sfOwnerNode] = *page; ctx_.view().insert(sle); - auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx); adjustOwnerCount(ctx_.view(), sleOwner, sponsor, 1, ctx_.journal); addSponsorToLedgerEntry(sle, sponsor); } diff --git a/src/xrpld/app/tx/detail/DepositPreauth.cpp b/src/xrpld/app/tx/detail/DepositPreauth.cpp index 31fc1b866b..ad8db71e74 100644 --- a/src/xrpld/app/tx/detail/DepositPreauth.cpp +++ b/src/xrpld/app/tx/detail/DepositPreauth.cpp @@ -173,13 +173,11 @@ 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; - } + auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); + if (auto const ret = checkInsufficientReserve( + view(), sleOwner, mPriorBalance, sponsor, 1); + !isTesSuccess(ret)) + return ret; // Preclaim already verified that the Preauth entry does not yet exist. // Create and populate the Preauth entry. @@ -206,7 +204,6 @@ DepositPreauth::doApply() slePreauth->setFieldU64(sfOwnerNode, *page); // If we succeeded, the new entry counts against the creator's reserve. - auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); adjustOwnerCount(view(), sleOwner, sponsor, 1, j_); addSponsorToLedgerEntry(slePreauth, sponsor); } @@ -226,13 +223,11 @@ 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; - } + auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); + if (auto const ret = checkInsufficientReserve( + view(), sleOwner, mPriorBalance, sponsor, 1); + !isTesSuccess(ret)) + return ret; // Preclaim already verified that the Preauth entry does not yet exist. // Create and populate the Preauth entry. @@ -272,7 +267,6 @@ DepositPreauth::doApply() slePreauth->setFieldU64(sfOwnerNode, *page); // If we succeeded, the new entry counts against the creator's reserve. - auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); adjustOwnerCount(view(), sleOwner, sponsor, 1, j_); addSponsorToLedgerEntry(slePreauth, sponsor); } diff --git a/src/xrpld/app/tx/detail/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp index 633841acfd..2ae070e7be 100644 --- a/src/xrpld/app/tx/detail/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -495,16 +495,21 @@ EscrowCreate::doApply() // Check reserve and funds availability STAmount const amount{ctx_.tx[sfAmount]}; - auto const reserve = - ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1); + auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); + if (auto const ret = checkInsufficientReserve( + ctx_.view(), sle, mSourceBalance, sponsor, 1); + !isTesSuccess(ret)) + return ret; - if (mSourceBalance < reserve) - return tecINSUFFICIENT_RESERVE; - - // 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(), + std::optional>(), + 1); + !isTesSuccess(ret)) return tecUNFUNDED; } @@ -599,7 +604,6 @@ EscrowCreate::doApply() } // increment owner count - auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx); adjustOwnerCount(ctx_.view(), sle, sponsor, 1, ctx_.journal); addSponsorToLedgerEntry(slep, sponsor); ctx_.view().update(sle); @@ -841,8 +845,10 @@ 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)) + auto const sponsor = getTxReserveSponsor(view, tx); + if (auto const ret = + checkInsufficientReserve(view, sleDest, xrpBalance, sponsor, 1); + !isTesSuccess(ret)) { JLOG(journal.trace()) << "Trust line does not exist. " "Insufficent reserve to create line."; @@ -968,11 +974,11 @@ 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; - } + auto const sponsor = getTxReserveSponsor(view, tx); + if (auto const ret = + checkInsufficientReserve(view, sleDest, xrpBalance, sponsor, 1); + !isTesSuccess(ret)) + return ret; if (auto const ter = MPTokenAuthorize::createMPToken(view, mptID, receiver, 0); @@ -982,7 +988,6 @@ escrowUnlockApplyHelper( } // update owner count. - auto const sponsor = getTxReserveSponsor(view, tx); adjustOwnerCount(view, sleDest, sponsor, 1, journal); addSponsorToLedgerEntry(sleDest, sponsor); } diff --git a/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp b/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp index 7ab27891b2..c62db50ffc 100644 --- a/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp +++ b/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp @@ -225,13 +225,14 @@ MPTokenAuthorize::authorize( // 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 (args.priorBalance < reserveCreate) - return tecINSUFFICIENT_RESERVE; + auto const sponsor = getTxReserveSponsor(view, tx); + if (sleAcct->getFieldU32(sfOwnerCount) >= 2) + { + if (auto const ret = checkInsufficientReserve( + view, sleAcct, args.priorBalance, sponsor, 1); + !isTesSuccess(ret)) + return ret; + } auto const mptokenKey = keylet::mptoken(args.mptIssuanceID, args.account); @@ -245,7 +246,6 @@ MPTokenAuthorize::authorize( view.insert(mptoken); // Update owner count. - auto const sponsor = getTxReserveSponsor(view, tx); adjustOwnerCount(view, sleAcct, sponsor, 1, journal); addSponsorToLedgerEntry(mptoken, sponsor); diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp index 2db460076e..af21c0d990 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp @@ -93,16 +93,18 @@ 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); + auto const sponsor = getTxReserveSponsor(view, tx); + if (args.priorBalance) + { + if (auto const ret = checkInsufficientReserve( + view, acct, *(args.priorBalance), sponsor, 1); + !isTesSuccess(ret)) + return Unexpected(ret); // tecINSUFFICIENT_RESERVE + } auto const mptId = makeMptID(args.sequence, args.account); auto const mptIssuanceKeylet = keylet::mptIssuance(mptId); - auto const sponsor = getTxReserveSponsor(view, tx); - // create the MPTokenIssuance { auto const ownerNode = view.dirInsert( diff --git a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp index ab74e5ac39..d145f4b5be 100644 --- a/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp +++ b/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp @@ -466,10 +466,13 @@ NFTokenAcceptOffer::transferNFToken( auto const buyerOwnerCountAfter = sleBuyer->getFieldU32(sfOwnerCount); if (buyerOwnerCountAfter > buyerOwnerCountBefore) { - if (auto const reserve = - view().fees().accountReserve(buyerOwnerCountAfter); - buyerBalance < reserve) - return tecINSUFFICIENT_RESERVE; + auto const sponsor = account_ == buyer + ? getTxReserveSponsor(ctx_.view(), ctx_.tx) + : std::optional>(); + if (auto const ret = checkInsufficientReserve( + ctx_.view(), sleBuyer, buyerBalance, sponsor, 0); + !isTesSuccess(ret)) + return ret; } } diff --git a/src/xrpld/app/tx/detail/NFTokenMint.cpp b/src/xrpld/app/tx/detail/NFTokenMint.cpp index f139c40aed..313111f08a 100644 --- a/src/xrpld/app/tx/detail/NFTokenMint.cpp +++ b/src/xrpld/app/tx/detail/NFTokenMint.cpp @@ -352,9 +352,15 @@ NFTokenMint::doApply() view().read(keylet::account(account_))->getFieldU32(sfOwnerCount); ownerCountAfter > ownerCountBefore) { - if (auto const reserve = view().fees().accountReserve(ownerCountAfter); - mPriorBalance < reserve) - return tecINSUFFICIENT_RESERVE; + auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx); + if (auto const ret = checkInsufficientReserve( + ctx_.view(), + view().read(keylet::account(account_)), + mPriorBalance, + sponsor, + 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 f2b2e12940..6df2a053b6 100644 --- a/src/xrpld/app/tx/detail/NFTokenUtils.cpp +++ b/src/xrpld/app/tx/detail/NFTokenUtils.cpp @@ -1044,12 +1044,14 @@ 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; + auto const acct = view.read(acctKeylet); + auto const sponsor = getTxReserveSponsor(view, tx); + if (auto const ret = + checkInsufficientReserve(view, acct, priorBalance, sponsor, 1); + !isTesSuccess(ret)) + return ret; auto const offerID = keylet::nftoffer(acctID, seqProxy.value()); - auto const sponsor = getTxReserveSponsor(view, tx); // Create the offer: { diff --git a/src/xrpld/app/tx/detail/PayChan.cpp b/src/xrpld/app/tx/detail/PayChan.cpp index abfc15a11e..aaaee51f50 100644 --- a/src/xrpld/app/tx/detail/PayChan.cpp +++ b/src/xrpld/app/tx/detail/PayChan.cpp @@ -204,13 +204,15 @@ 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); + auto const sponsor = getTxReserveSponsor(ctx.view, ctx.tx); + if (auto const ret = + checkInsufficientReserve(ctx.view, sle, balance, sponsor, 1); + !isTesSuccess(ret)) + return ret; - if (balance < reserve) - return tecINSUFFICIENT_RESERVE; - - if (balance < reserve + ctx.tx[sfAmount]) + if (auto const ret = checkInsufficientReserve( + ctx.view, sle, balance - ctx.tx[sfAmount], sponsor, 1); + !isTesSuccess(ret)) return tecUNFUNDED; } @@ -392,13 +394,19 @@ PayChanFund::doApply() { // Check reserve and funds availability auto const balance = (*sle)[sfBalance]; - auto const reserve = - ctx_.view().fees().accountReserve((*sle)[sfOwnerCount]); + auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx); + if (auto const ret = + checkInsufficientReserve(ctx_.view(), sle, balance, sponsor, 0); + !isTesSuccess(ret)) + return ret; - if (balance < reserve) - return tecINSUFFICIENT_RESERVE; - - if (balance < reserve + ctx_.tx[sfAmount]) + if (auto const ret = checkInsufficientReserve( + ctx_.view(), + sle, + balance - ctx_.tx[sfAmount], + std::optional>(), + 0); + !isTesSuccess(ret)) return tecUNFUNDED; } diff --git a/src/xrpld/app/tx/detail/PermissionedDomainSet.cpp b/src/xrpld/app/tx/detail/PermissionedDomainSet.cpp index b80cf88504..17c5da3740 100644 --- a/src/xrpld/app/tx/detail/PermissionedDomainSet.cpp +++ b/src/xrpld/app/tx/detail/PermissionedDomainSet.cpp @@ -121,10 +121,11 @@ 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; + auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx); + if (auto const ret = checkInsufficientReserve( + ctx_.view(), ownerSle, balance, sponsor, 1); + !isTesSuccess(ret)) + return ret; Keylet const pdKeylet = keylet::permissionedDomain( account_, ctx_.tx.getFieldU32(sfSequence)); @@ -142,7 +143,6 @@ PermissionedDomainSet::doApply() slePd->setFieldU64(sfOwnerNode, *page); // If we succeeded, the new entry counts against the creator's reserve. - auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx); adjustOwnerCount(view(), ownerSle, sponsor, 1, ctx_.journal); addSponsorToLedgerEntry(slePd, sponsor); view().insert(slePd); diff --git a/src/xrpld/app/tx/detail/SetOracle.cpp b/src/xrpld/app/tx/detail/SetOracle.cpp index 7e7cff6e05..6c5d99ae6f 100644 --- a/src/xrpld/app/tx/detail/SetOracle.cpp +++ b/src/xrpld/app/tx/detail/SetOracle.cpp @@ -172,12 +172,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; + auto const sponsor = getTxReserveSponsor(ctx.view, ctx.tx); + if (auto const ret = checkInsufficientReserve( + ctx.view, sleSetter, balance, sponsor, 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 c3c7c2a354..418d542e4c 100644 --- a/src/xrpld/app/tx/detail/SetSignerList.cpp +++ b/src/xrpld/app/tx/detail/SetSignerList.cpp @@ -354,9 +354,6 @@ SetSignerList::replaceSignerList() if (!sle) 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}; @@ -367,14 +364,14 @@ 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; + auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx); + if (auto const ret = checkInsufficientReserve( + ctx_.view(), sle, mPriorBalance, sponsor, addedOwnerCount); + !isTesSuccess(ret)) + return ret; // Everything's ducky. Add the ltSIGNER_LIST to the ledger. auto signerList = std::make_shared(signerListKeylet); @@ -396,7 +393,6 @@ SetSignerList::replaceSignerList() // If we succeeded, the new entry counts against the // creator's reserve. - auto const sponsor = getTxReserveSponsor(ctx_.view(), ctx_.tx); adjustOwnerCount(view(), sle, sponsor, addedOwnerCount, viewJ); addSponsorToLedgerEntry(signerList, sponsor); return tesSUCCESS; diff --git a/src/xrpld/app/tx/detail/SetTrust.cpp b/src/xrpld/app/tx/detail/SetTrust.cpp index ef7061bb2e..25b9eb33b6 100644 --- a/src/xrpld/app/tx/detail/SetTrust.cpp +++ b/src/xrpld/app/tx/detail/SetTrust.cpp @@ -402,9 +402,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( @@ -455,6 +453,8 @@ SetTrust::doApply() SLE::pointer sleRippleState = view().peek(keylet::line(account_, uDstAccountID, currency)); + auto const sponsor = getTxReserveSponsor(view(), ctx_.tx); + if (sleRippleState) { STAmount saLowBalance; @@ -678,7 +678,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, sponsor, 0); + !freeTrustLine && bReserveIncrease && !isTesSuccess(ret)) { JLOG(j_.trace()) << "Delay transaction: Insufficent reserve to " "add trust line."; @@ -707,8 +709,14 @@ 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( + ctx_.view(), + sle, + mPriorBalance, + sponsor, + 1); + !freeTrustLine && + !isTesSuccess(ret)) // Reserve is not scaled by load. { JLOG(j_.trace()) << "Delay transaction: Line does not exist. " "Insufficent reserve to create line."; diff --git a/src/xrpld/app/tx/detail/VaultCreate.cpp b/src/xrpld/app/tx/detail/VaultCreate.cpp index 77ed8c8c76..ce5b0d6c4e 100644 --- a/src/xrpld/app/tx/detail/VaultCreate.cpp +++ b/src/xrpld/app/tx/detail/VaultCreate.cpp @@ -177,9 +177,10 @@ VaultCreate::doApply() auto const sponsor = getTxReserveSponsor(view(), tx); adjustOwnerCount(view(), owner, sponsor, 1, j_); addSponsorToLedgerEntry(vault, sponsor); - auto ownerCount = owner->at(sfOwnerCount); - if (mPriorBalance < view().fees().accountReserve(ownerCount)) - return tecINSUFFICIENT_RESERVE; + if (auto const ret = + checkInsufficientReserve(view(), owner, mPriorBalance, sponsor, 0); + !isTesSuccess(ret)) + return ret; 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 5c7a6dc0d6..56f493e003 100644 --- a/src/xrpld/app/tx/detail/XChainBridge.cpp +++ b/src/xrpld/app/tx/detail/XChainBridge.cpp @@ -1066,11 +1066,11 @@ applyCreateAccountAttestations( // Check reserve auto const balance = (*sleDoor)[sfBalance]; - auto const reserve = - psb.fees().accountReserve((*sleDoor)[sfOwnerCount] + 1); - - if (balance < reserve) - return Unexpected(tecINSUFFICIENT_RESERVE); + auto const sponsor = std::optional>(); + if (auto const ret = + checkInsufficientReserve(psb, sleDoor, balance, sponsor, 1); + !isTesSuccess(ret)) + return Unexpected(ret); // tecINSUFFICIENT_RESERVE } std::vector atts; @@ -1507,11 +1507,11 @@ 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; + auto const sponsor = getTxReserveSponsor(ctx.view, ctx.tx); + if (auto const ret = + checkInsufficientReserve(ctx.view, sleAcc, balance, sponsor, 1); + !isTesSuccess(ret)) + return ret; } return tesSUCCESS; @@ -2074,11 +2074,11 @@ 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; + auto const sponsor = getTxReserveSponsor(ctx.view, ctx.tx); + if (auto const ret = + checkInsufficientReserve(ctx.view, sleAcc, balance, sponsor, 1); + !isTesSuccess(ret)) + return ret; } return tesSUCCESS; diff --git a/src/xrpld/ledger/View.h b/src/xrpld/ledger/View.h index c7f45184ef..2c797a7120 100644 --- a/src/xrpld/ledger/View.h +++ b/src/xrpld/ledger/View.h @@ -452,11 +452,22 @@ areCompatible( beast::Journal::Stream& s, char const* reason); +TER +checkInsufficientReserve( + ReadView const& view, + std::shared_ptr accSle, + STAmount const& accBalance, + std::optional> const& _sponsorSle, + std::int32_t ownerCountDelta); + std::optional> getTxReserveSponsor(ApplyView& view, STTx const& tx); +std::optional> +getTxReserveSponsor(ReadView const& view, STTx const& tx); + std::optional> -getLedgerEntryReserveSponsor(ApplyView& view, SLE::ref sle); +getLedgerEntryReserveSponsor(ApplyView& view, std::shared_ptr sle); void addSponsorToLedgerEntry( diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index afa6ce6a21..3017ebb729 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -1021,6 +1021,43 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal) return std::nullopt; } +TER +checkInsufficientReserve( + ReadView const& view, + std::shared_ptr accSle, + STAmount const& accBalance, + std::optional> const& _sponsorSle, + std::int32_t ownerCountDelta) +{ + if (_sponsorSle.has_value()) + { + auto const sponsorSle = _sponsorSle.value(); + auto const sponsorBalance = sponsorSle->getFieldAmount(sfBalance); + STAmount const sponsorReserve{view.fees().accountReserve( + sponsorSle->getFieldU32(sfOwnerCount), + sponsorSle->getFieldU32(sfSponsoredOwnerCount), + sponsorSle->getFieldU32(sfSponsoringOwnerCount) + ownerCountDelta, + sponsorSle->isFieldPresent(sfSponsor), + sponsorSle->getFieldU32(sfSponsoringAccountCount))}; + + if (sponsorBalance < sponsorReserve) + return tecINSUFFICIENT_RESERVE; + } + else + { + STAmount const reserve{view.fees().accountReserve( + accSle->getFieldU32(sfOwnerCount) + ownerCountDelta, + accSle->getFieldU32(sfSponsoredOwnerCount), + accSle->getFieldU32(sfSponsoringOwnerCount), + accSle->isFieldPresent(sfSponsor), + accSle->getFieldU32(sfSponsoringAccountCount))}; + + if (accBalance < reserve) + return tecINSUFFICIENT_RESERVE; + } + return tesSUCCESS; +} + std::optional> getTxReserveSponsor(ApplyView& view, STTx const& tx) { @@ -1037,8 +1074,24 @@ getTxReserveSponsor(ApplyView& view, STTx const& tx) return std::nullopt; } +std::optional> +getTxReserveSponsor(ReadView const& view, STTx const& tx) +{ + if (tx.isFieldPresent(sfSponsor)) + { + auto const sponsorObj = tx.getFieldObject(sfSponsor); + auto const flags = sponsorObj.getFlags(); + auto const sponsorID = sponsorObj.getAccountID(sfAccount); + if (flags & tfSponsorReserve) + { + return view.read(keylet::account(sponsorID)); + } + } + return std::nullopt; +} + std::optional> -getLedgerEntryReserveSponsor(ApplyView& view, SLE::ref sle) +getLedgerEntryReserveSponsor(ApplyView& view, std::shared_ptr sle) { if (sle->isFieldPresent(sfSponsorAccount)) return view.peek(keylet::account(sle->getAccountID(sfSponsorAccount)));