diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index ca8867f0f5..270311173f 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -812,6 +812,7 @@ accountSend( AccountID const& to, STAmount const& saAmount, beast::Journal j, + std::optional const& sponsorAcc = std::nullopt, WaiveTransferFee waiveFee = WaiveTransferFee::No); [[nodiscard]] TER diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index d2fd26809b..69127f4b57 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -1801,6 +1801,9 @@ trustDelete( return tefBAD_LEDGER; } + removeSponsorFromLedgerEntry(sleRippleState, sfHighSponsorAccount); + removeSponsorFromLedgerEntry(sleRippleState, sfLowSponsorAccount); + JLOG(j.trace()) << "trustDelete: Deleting ripple line: state"; view.erase(sleRippleState); @@ -1876,6 +1879,7 @@ rippleCreditIOU( AccountID const& uReceiverID, STAmount const& saAmount, bool bCheckIssuer, + std::optional const& sponsorAccount, beast::Journal j) { AccountID const& issuer = saAmount.getIssuer(); @@ -2029,7 +2033,7 @@ rippleCreditIOU( saReceiverLimit, 0, 0, - std::nullopt, + sponsorAccount, j); } @@ -2044,6 +2048,7 @@ rippleSendIOU( STAmount const& saAmount, STAmount& saActual, beast::Journal j, + std::optional const& sponsorAccount, WaiveTransferFee waiveFee) { auto const issuer = saAmount.getIssuer(); @@ -2058,8 +2063,8 @@ rippleSendIOU( if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount()) { // Direct send: redeeming IOUs and/or sending own IOUs. - auto const ter = - rippleCreditIOU(view, uSenderID, uReceiverID, saAmount, false, j); + auto const ter = rippleCreditIOU( + view, uSenderID, uReceiverID, saAmount, false, sponsorAccount, j); if (view.rules().enabled(featureDeletableAccounts) && ter != tesSUCCESS) return ter; saActual = saAmount; @@ -2079,11 +2084,12 @@ rippleSendIOU( << " : deliver=" << saAmount.getFullText() << " cost=" << saActual.getFullText(); - TER terResult = - rippleCreditIOU(view, issuer, uReceiverID, saAmount, true, j); + TER terResult = rippleCreditIOU( + view, issuer, uReceiverID, saAmount, true, sponsorAccount, j); if (tesSUCCESS == terResult) - terResult = rippleCreditIOU(view, uSenderID, issuer, saActual, true, j); + terResult = rippleCreditIOU( + view, uSenderID, issuer, saActual, true, sponsorAccount, j); return terResult; } @@ -2095,6 +2101,7 @@ accountSendIOU( AccountID const& uReceiverID, STAmount const& saAmount, beast::Journal j, + std::optional const& sponsorAccount, WaiveTransferFee waiveFee) { if (view.rules().enabled(fixAMMv1_1)) @@ -2126,7 +2133,14 @@ accountSendIOU( << saAmount.getFullText(); return rippleSendIOU( - view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee); + view, + uSenderID, + uReceiverID, + saAmount, + saActual, + j, + sponsorAccount, + waiveFee); } /* XRP send which does not check reserve and can do pure adjustment. @@ -2368,13 +2382,20 @@ accountSend( AccountID const& uReceiverID, STAmount const& saAmount, beast::Journal j, + std::optional const& sponsorAcc, WaiveTransferFee waiveFee) { return std::visit( [&](TIss const& issue) { if constexpr (std::is_same_v) return accountSendIOU( - view, uSenderID, uReceiverID, saAmount, j, waiveFee); + view, + uSenderID, + uReceiverID, + saAmount, + j, + sponsorAcc, + waiveFee); else return accountSendMPT( view, uSenderID, uReceiverID, saAmount, j, waiveFee); @@ -2423,7 +2444,7 @@ updateTrustLine( // Clear the reserve of the sender, possibly delete the line! auto const currentSponsor = getLedgerEntryReserveSponsor( view, - sle, + state, !bSenderHigh ? sfLowSponsorAccount : sfHighSponsorAccount); adjustOwnerCount(view, sle, currentSponsor, -1, j); @@ -3021,7 +3042,11 @@ deleteAMMTrustLine( if (!(sleState->getFlags() & uFlags)) return tecINTERNAL; - adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, std::nullopt, -1, j); + auto const sponsorSle = getLedgerEntryReserveSponsor( + view, sleState, !ammLow ? sfLowSponsorAccount : sfHighSponsorAccount); + adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, sponsorSle, -1, j); + removeSponsorFromLedgerEntry( + sleState, !ammLow ? sfLowSponsorAccount : sfHighSponsorAccount); return tesSUCCESS; } @@ -3040,7 +3065,13 @@ rippleCredit( if constexpr (std::is_same_v) { return rippleCreditIOU( - view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j); + view, + uSenderID, + uReceiverID, + saAmount, + bCheckIssuer, + std::nullopt, + j); } else { diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index 3ba39ada4b..fd2ea8e752 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -899,6 +899,338 @@ public: env.close(); } + void + testAMM() + { + testcase("AMM"); + using namespace test::jtx; + Account const alice("alice"); + Account const bob("bob"); + Account const gw("gw"); + Account const sponsor("sponsor"); + + auto const USD = gw["USD"]; + auto const EUR = gw["EUR"]; + + auto const ammCreate = [&](Env& env, + Account const& account, + STAmount const& amount1, + STAmount const& amount2) { + Json::Value jv; + jv[jss::TransactionType] = jss::AMMCreate; + jv[jss::Account] = account.human(); + jv[jss::Amount] = amount1.getJson(JsonOptions::none); + jv[jss::Amount2] = amount2.getJson(JsonOptions::none); + jv[jss::TradingFee] = 0; + jv[jss::Fee] = + std::to_string(env.current()->fees().increment.drops()); + return jv; + }; + + auto const ammDeposit = [&](Env& env, + Account const& account, + STAmount const& amount1, + STAmount const& amount2) { + Json::Value jv; + jv[jss::TransactionType] = jss::AMMDeposit; + jv[jss::Account] = account.human(); + jv[jss::Asset] = + STIssue(sfAsset, amount1.issue()).getJson(JsonOptions::none); + jv[jss::Asset2] = + STIssue(sfAsset, amount2.issue()).getJson(JsonOptions::none); + jv[jss::Amount] = amount1.value().getJson(JsonOptions::none); + jv[jss::Amount2] = amount2.value().getJson(JsonOptions::none); + jv[jss::Flags] = tfTwoAsset; + return jv; + }; + + { + // AMMCreate + // - sponsor LPToken + // - doesn't sponsor AMM object + Env env{*this, testable_amendments()}; + env.fund(XRP(10000), alice, gw, sponsor); + env.close(); + + env(trust(alice, USD(10000))); + env(trust(alice, EUR(10000))); + env.close(); + + env(pay(gw, alice, USD(1000))); + env(pay(gw, alice, EUR(1000))); + env.close(); + + { + // AMMCreate doesn't check INSUFFICIENT_RESERVE now + // see: https://github.com/XRPLF/rippled/issues/5812 + // check INSUFFICIENT_RESERVE + adjustAccountXRPBalance( + env, sponsor, reserve(env, 1) - drops(1)); + + env(ammCreate(env, alice, USD(100), EUR(100)), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor), + ter(tecINSUF_RESERVE_LINE)); + env.close(); + adjustAccountXRPBalance(env, sponsor, reserve(env, 2)); + } + + env(ammCreate(env, alice, USD(100), EUR(100)), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + auto const amm = + env.current()->read(keylet::amm(USD.issue(), EUR.issue())); + auto const ammAccount = + Account("amm", amm->getAccountID(sfAccount)); + + BEAST_EXPECT( + ownerCount(env, alice) == 3); // RippleState (USD,EUR/LP Token) + BEAST_EXPECT(ownerCount(env, ammAccount) == 2); // USD, EUR + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); // LPToken + BEAST_EXPECT(sponsoredOwnerCount(env, ammAccount) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); // LPToken + BEAST_EXPECT(!env.le(keylet::amm(USD.issue(), EUR.issue())) + ->isFieldPresent(sfSponsorAccount)); + } + { + // AMMDeposit + // - sponsor new LPToken + Env env{*this, testable_amendments()}; + env.fund(XRP(10000), alice, bob, gw, sponsor); + env.close(); + + env(trust(alice, USD(10000))); + env(trust(alice, EUR(10000))); + env(trust(bob, USD(10000))); + env(trust(bob, EUR(10000))); + env.close(); + + env(pay(gw, alice, USD(1000))); + env(pay(gw, alice, EUR(1000))); + env(pay(gw, bob, USD(1000))); + env(pay(gw, bob, EUR(1000))); + env.close(); + + env(ammCreate(env, alice, USD(100), EUR(100))); + env.close(); + + BEAST_EXPECT(ownerCount(env, bob) == 2); // RippleState (USD,EUR) + + { + // check INSUFFICIENT_RESERVE + adjustAccountXRPBalance( + env, sponsor, reserve(env, 1) - drops(1)); + + env(ammDeposit(env, bob, USD(100), EUR(100)), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor), + ter(tecINSUF_RESERVE_LINE)); + env.close(); + adjustAccountXRPBalance(env, sponsor, reserve(env, 2)); + } + + env(ammDeposit(env, bob, USD(100), EUR(100)), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + BEAST_EXPECT( + ownerCount(env, bob) == 3); // RippleState (USD,EUR/LP Token) + BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); // LPToken + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); // LPToken + } + { + // AMMWithdraw + { + // Single Asset Withdraw + // - sponsor new RippleState + Env env{*this, testable_amendments()}; + env.fund(XRP(10000), alice, bob, gw, sponsor); + env.close(); + + env(trust(alice, USD(10000))); + env(trust(alice, EUR(10000))); + env.close(); + + env(pay(gw, alice, USD(1000))); + env(pay(gw, alice, EUR(1000))); + env.close(); + + env(ammCreate(env, alice, USD(1000), EUR(1000)), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + env(trust(alice, USD(0))); + env(trust(alice, EUR(0))); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); // LPToken + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); // LPToken + BEAST_EXPECT( + sponsoringOwnerCount(env, sponsor) == 1); // LPToken + + Json::Value jv; + jv[jss::TransactionType] = jss::AMMWithdraw; + jv[jss::Account] = alice.human(); + jv[jss::Asset] = + STIssue(sfAsset, USD.issue()).getJson(JsonOptions::none); + jv[jss::Asset2] = + STIssue(sfAsset, EUR.issue()).getJson(JsonOptions::none); + jv[jss::Amount] = USD(100).value().getJson(JsonOptions::none); + jv[jss::Flags] = tfSingleAsset; + + { + env(ticket::create( + sponsor, 1)); // adjust for free trustline + env.close(); + BEAST_EXPECT(ownerCount(env, sponsor) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + // check INSUFFICIENT_RESERVE + adjustAccountXRPBalance( + env, sponsor, reserve(env, 2) - drops(1)); + env(jv, + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor), + ter(tecINSUFFICIENT_RESERVE)); + env.close(); + adjustAccountXRPBalance(env, sponsor, reserve(env, 3)); + } + + env(jv, + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 2); // USD, LPToken + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2); + } + { + // Double Asset Withdraw + // - sponsor new RippleState * 2 + // - remove sponsored LPToken + Env env{*this, testable_amendments()}; + env.fund(XRP(10000), alice, bob, gw, sponsor); + env.close(); + + env(trust(alice, USD(10000))); + env(trust(alice, EUR(10000))); + env.close(); + + env(pay(gw, alice, USD(1000))); + env(pay(gw, alice, EUR(1000))); + env.close(); + + env(ammCreate(env, alice, USD(1000), EUR(1000)), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + env(trust(alice, USD(0))); + env(trust(alice, EUR(0))); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); // LPToken + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + + Json::Value jv; + jv[jss::TransactionType] = jss::AMMWithdraw; + jv[jss::Account] = alice.human(); + jv[jss::Asset] = + STIssue(sfAsset, USD.issue()).getJson(JsonOptions::none); + jv[jss::Asset2] = + STIssue(sfAsset, EUR.issue()).getJson(JsonOptions::none); + jv[jss::Flags] = tfWithdrawAll; + + { + env(ticket::create( + sponsor, 1)); // adjust for free trustline + env.close(); + BEAST_EXPECT(ownerCount(env, sponsor) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + // check INSUFFICIENT_RESERVE for RippleStates * 2 + adjustAccountXRPBalance( + env, sponsor, reserve(env, 3) - drops(1)); + env(jv, + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor), + ter(tecINSUFFICIENT_RESERVE)); + env.close(); + adjustAccountXRPBalance(env, sponsor, reserve(env, 4)); + } + + env(jv, + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 2); // USD, EUR + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2); + } + } + { + // AMMClawback + // - doesn't sponsor holder's new RippleState + // - remove sponsored LPToken + Account const gw2("gw2"); + auto const EUR2 = gw2["EUR"]; + + Env env{*this, testable_amendments()}; + env.fund(XRP(10000), alice, gw, gw2, sponsor); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + env(trust(alice, USD(10000))); + env(trust(alice, EUR2(10000))); + env.close(); + + env(pay(gw, alice, USD(100))); + env(pay(gw2, alice, EUR2(100))); + env.close(); + + env(ammCreate(env, alice, USD(100), EUR2(100)), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + env(trust(alice, USD(0))); + env(trust(alice, EUR2(0))); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); // LPToken + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + { + // doesn't sponsor holder's new RippleState + env(amm::ammClawback(gw, alice, USD, EUR2, USD(10)), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 2); // LPToken, EUR2 + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); + } + { + env(amm::ammClawback(gw, alice, USD, EUR2, std::nullopt), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 1); // EUR2 + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); + } + } + } + void testCheck() { @@ -3166,6 +3498,7 @@ public: { // TODO: add checks fo InsufficientReserve for Sponsoring ledger entry testRequireFlag(); + testAMM(); testCheck(); testOffer(); testTicket(); diff --git a/src/xrpld/app/paths/AMMOffer.h b/src/xrpld/app/paths/AMMOffer.h index 3c218c0f5e..9834d070c1 100644 --- a/src/xrpld/app/paths/AMMOffer.h +++ b/src/xrpld/app/paths/AMMOffer.h @@ -122,7 +122,8 @@ public: static TER send(Args&&... args) { - return accountSend(std::forward(args)..., WaiveTransferFee::Yes); + return accountSend( + std::forward(args)..., std::nullopt, WaiveTransferFee::Yes); } bool diff --git a/src/xrpld/app/tx/detail/AMMCreate.cpp b/src/xrpld/app/tx/detail/AMMCreate.cpp index 03c972f1cd..e922530f5f 100644 --- a/src/xrpld/app/tx/detail/AMMCreate.cpp +++ b/src/xrpld/app/tx/detail/AMMCreate.cpp @@ -141,8 +141,10 @@ AMMCreate::preclaim(PreclaimContext const& ctx) return terNO_RIPPLE; } + auto const sponsor = getTxReserveSponsorAccountID(ctx.tx); // Check the reserve for LPToken trustline - STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j); + STAmount const xrpBalance = + xrpLiquid(ctx.view, sponsor.value_or(accountID), 1, ctx.j); // Insufficient reserve if (xrpBalance <= beast::zero) { @@ -275,7 +277,9 @@ applyCreate( sb.insert(ammSle); // Send LPT to LP. - auto res = accountSend(sb, accountId, account_, lpTokens, ctx_.journal); + auto const sponsor = getTxReserveSponsorAccountID(ctx_.tx); + auto res = + accountSend(sb, accountId, account_, lpTokens, ctx_.journal, sponsor); if (res != tesSUCCESS) { JLOG(j_.debug()) << "AMM Instance: failed to send LPT " << lpTokens; @@ -289,6 +293,7 @@ applyCreate( accountId, amount, ctx_.journal, + std::nullopt, // don't sponsor for AMM Trustline WaiveTransferFee::Yes)) return res; // Set AMM flag on AMM trustline diff --git a/src/xrpld/app/tx/detail/AMMDeposit.cpp b/src/xrpld/app/tx/detail/AMMDeposit.cpp index 614d788c71..5701bab094 100644 --- a/src/xrpld/app/tx/detail/AMMDeposit.cpp +++ b/src/xrpld/app/tx/detail/AMMDeposit.cpp @@ -167,6 +167,8 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) { auto const accountID = ctx.tx[sfAccount]; + auto const sponsor = getTxReserveSponsorAccountID(ctx.tx); + auto const ammSle = ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2])); if (!ammSle) @@ -224,7 +226,8 @@ 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) + if (xrpLiquid(ctx.view, sponsor.value_or(accountID), !sle, ctx.j) >= + deposit) return TER(tesSUCCESS); if (sle) return tecUNFUNDED_AMM; @@ -352,7 +355,8 @@ 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); + STAmount const xrpBalance = + xrpLiquid(ctx.view, sponsor.value_or(accountID), 1, ctx.j); // Insufficient reserve if (xrpBalance <= beast::zero) { @@ -507,6 +511,8 @@ AMMDeposit::deposit( std::optional const& lpTokensDepositMin, std::uint16_t tfee) { + auto const sponsor = getTxReserveSponsorAccountID(ctx_.tx); + // Check account has sufficient funds. // Return true if it does, false otherwise. auto checkBalance = [&](auto const& depositAmount) -> TER { @@ -518,7 +524,8 @@ AMMDeposit::deposit( // Adjust the reserve if LP doesn't have LPToken trustline auto const sle = view.read( keylet::line(account_, lpIssue.account, lpIssue.currency)); - if (xrpLiquid(view, account_, !sle, j_) >= depositAmount) + if (xrpLiquid(view, sponsor.value_or(account_), !sle, j_) >= + depositAmount) return tesSUCCESS; } else if ( @@ -578,6 +585,7 @@ AMMDeposit::deposit( ammAccount, amountDepositActual, ctx_.journal, + std::nullopt, // don't sponsor for AMM Trustline WaiveTransferFee::Yes); if (res != tesSUCCESS) { @@ -604,6 +612,7 @@ AMMDeposit::deposit( ammAccount, *amount2DepositActual, ctx_.journal, + std::nullopt, // don't sponsor for AMM Trustline WaiveTransferFee::Yes); if (res != tesSUCCESS) { @@ -615,7 +624,12 @@ AMMDeposit::deposit( // Deposit LP tokens res = accountSend( - view, ammAccount, account_, lpTokensDepositActual, ctx_.journal); + view, + ammAccount, + account_, + lpTokensDepositActual, + ctx_.journal, + sponsor); if (res != tesSUCCESS) { JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit LPTokens"; diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.cpp b/src/xrpld/app/tx/detail/AMMWithdraw.cpp index e0aa797f16..f19318f28e 100644 --- a/src/xrpld/app/tx/detail/AMMWithdraw.cpp +++ b/src/xrpld/app/tx/detail/AMMWithdraw.cpp @@ -586,6 +586,12 @@ AMMWithdraw::withdraw( return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}}; } + // this is also called from AMMClawback, but only AMMWithdraw does sponsor + // the new trustline + auto const sponsor = tx[sfAccount] == account + ? getTxReserveSponsorAccountID(tx) + : std::nullopt; + // Check the reserve in case a trustline has to be created bool const enabledFixAMMv1_2 = view.rules().enabled(fixAMMv1_2); auto sufficientReserve = [&](Issue const& issue) -> TER { @@ -593,20 +599,20 @@ AMMWithdraw::withdraw( return tesSUCCESS; if (!view.exists(keylet::line(account, issue))) { - auto const sleAccount = view.read(keylet::account(account)); + auto const sleAccount = + view.read(keylet::account(sponsor.value_or(account))); if (!sleAccount) return tecINTERNAL; // LCOV_EXCL_LINE auto const balance = (*sleAccount)[sfBalance].xrp(); - std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount); - - if (ownerCount >= 2) + std::uint32_t const count = ownerCount(sleAccount); + if (count >= 2) { - auto const sponsor = getTxReserveSponsor(view, tx); if (auto const ret = checkInsufficientReserve( view, sleAccount, std::max(priorBalance, balance), - sponsor, + sponsor ? view.read(keylet::account(*sponsor)) + : std::optional>{}, 1); !isTesSuccess(ret)) return ret; @@ -625,6 +631,7 @@ AMMWithdraw::withdraw( account, amountWithdrawActual, journal, + sponsor, WaiveTransferFee::Yes); if (res != tesSUCCESS) { @@ -648,6 +655,7 @@ AMMWithdraw::withdraw( account, *amount2WithdrawActual, journal, + sponsor, WaiveTransferFee::Yes); if (res != tesSUCCESS) { diff --git a/src/xrpld/app/tx/detail/VaultClawback.cpp b/src/xrpld/app/tx/detail/VaultClawback.cpp index a81deed669..2d09173ac2 100644 --- a/src/xrpld/app/tx/detail/VaultClawback.cpp +++ b/src/xrpld/app/tx/detail/VaultClawback.cpp @@ -278,6 +278,7 @@ VaultClawback::doApply() vaultAccount, sharesDestroyed, j_, + std::nullopt, WaiveTransferFee::Yes); !isTesSuccess(ter)) return ter; @@ -317,6 +318,7 @@ VaultClawback::doApply() account_, assetsRecovered, j_, + std::nullopt, WaiveTransferFee::Yes); !isTesSuccess(ter)) return ter; diff --git a/src/xrpld/app/tx/detail/VaultDeposit.cpp b/src/xrpld/app/tx/detail/VaultDeposit.cpp index 377022b30e..efe187fc04 100644 --- a/src/xrpld/app/tx/detail/VaultDeposit.cpp +++ b/src/xrpld/app/tx/detail/VaultDeposit.cpp @@ -302,6 +302,8 @@ VaultDeposit::doApply() if (maximum != 0 && *vault->at(sfAssetsTotal) > maximum) return tecLIMIT_EXCEEDED; + auto const sponsor = getTxReserveSponsorAccountID(ctx_.tx); + // Transfer assets from depositor to vault. if (auto const ter = accountSend( view(), @@ -309,6 +311,7 @@ VaultDeposit::doApply() vaultAccount, assetsDeposited, j_, + sponsor, WaiveTransferFee::Yes); !isTesSuccess(ter)) return ter; @@ -335,6 +338,7 @@ VaultDeposit::doApply() account_, sharesCreated, j_, + sponsor, WaiveTransferFee::Yes); !isTesSuccess(ter)) return ter; diff --git a/src/xrpld/app/tx/detail/VaultWithdraw.cpp b/src/xrpld/app/tx/detail/VaultWithdraw.cpp index f3b47424e7..2216df5420 100644 --- a/src/xrpld/app/tx/detail/VaultWithdraw.cpp +++ b/src/xrpld/app/tx/detail/VaultWithdraw.cpp @@ -277,6 +277,7 @@ VaultWithdraw::doApply() view().update(vault); auto const& vaultAccount = vault->at(sfAccount); + auto const sponsor = getTxReserveSponsorAccountID(ctx_.tx); // Transfer shares from depositor to vault. if (auto const ter = accountSend( view(), @@ -284,6 +285,7 @@ VaultWithdraw::doApply() vaultAccount, sharesRedeemed, j_, + sponsor, WaiveTransferFee::Yes); !isTesSuccess(ter)) return ter; @@ -329,6 +331,7 @@ VaultWithdraw::doApply() dstAcct, assetsWithdrawn, j_, + sponsor, WaiveTransferFee::Yes); !isTesSuccess(ter)) return ter;