diff --git a/src/test/app/LoanBroker_test.cpp b/src/test/app/LoanBroker_test.cpp index 597e2ea75b7..7f11dc6496e 100644 --- a/src/test/app/LoanBroker_test.cpp +++ b/src/test/app/LoanBroker_test.cpp @@ -1436,6 +1436,87 @@ class LoanBroker_test : public beast::unit_test::suite }); } + void + testRIPD4323() + { + testcase << "RIPD-4323"; + using namespace jtx; + Account const issuer("issuer"); + Account const holder("holder"); + Account const& broker = issuer; + + auto test = [&](auto&& getToken) { + Env env(*this); + + env.fund(XRP(1'000), issuer, holder); + env.close(); + + auto const [token, deposit, err] = getToken(env); + + Vault vault(env); + auto const [tx, keylet] = + vault.create({.owner = broker, .asset = token.asset()}); + env(tx); + env.close(); + + env(vault.deposit( + {.depositor = broker, .id = keylet.key, .amount = deposit}), + ter(err)); + env.close(); + + auto const brokerKeylet = + keylet::loanbroker(broker, env.seq(broker)); + + env(loanBroker::set(broker, keylet.key)); + env.close(); + + env(loanBroker::coverDeposit(broker, brokerKeylet.key, deposit), + ter(err)); + env.close(); + }; + + test([&](Env&) { + // issuer can issue any amount + auto const token = issuer["IOU"]; + return std::make_tuple(token, token(1'000), tesSUCCESS); + }); + std::vector, // max amount + std::uint64_t, // deposit amount + TER>> // expected error + mptTests = { + // issuer can issue up to 2'000 tokens + {2'000, 4'000, 1'000, tesSUCCESS}, + // issuer can issue 500 tokens (250 VaultDeposit + + // 250 LoanBrokerCoverDeposit) + {2'000, 2'500, 250, tesSUCCESS}, + // issuer can issue 500 tokens (250 VaultDeposit + + // 250 LoanBrokerCoverDeposit). MaximumAmount is default. + {maxMPTokenAmount - 500, std::nullopt, 250, tesSUCCESS}, + // issuer can issue 500, and fails on depositing 1'000 + {2'000, 2'500, 1'000, tecINSUFFICIENT_FUNDS}, + // issuer has already issued MaximumAmount + {2'000, 2'000, 1'000, tecINSUFFICIENT_FUNDS}, + // issuer has already issued MaximumAmount. MaximumAmount is + // default. + {maxMPTokenAmount, std::nullopt, 250, tecINSUFFICIENT_FUNDS}, + }; + for (auto const& [pay, max, deposit, err] : mptTests) + { + test([&](Env& env) -> std::tuple { + MPT const token = MPTTester( + {.env = env, + .issuer = issuer, + .holders = {holder}, + .pay = pay, + .flags = MPTDEXFlags, + .maxAmt = max}); + return std::make_tuple(token, token(deposit), err); + }); + } + } + public: void run() override @@ -1451,6 +1532,8 @@ class LoanBroker_test : public beast::unit_test::suite testInvalidLoanBrokerSet(); testRequireAuth(); + testRIPD4323(); + // TODO: Write clawback failure tests with an issuer / MPT that doesn't // have the right flags set. } diff --git a/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp b/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp index 4e9e0e9c054..623a4cf9c52 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp @@ -75,7 +75,7 @@ LoanBrokerCoverDeposit::preclaim(PreclaimContext const& ctx) requireAuth(ctx.view, vaultAsset, account, AuthType::StrongAuth)) return ret; - if (accountHolds( + if (accountSpendable( ctx.view, account, vaultAsset, diff --git a/src/xrpld/app/tx/detail/VaultDeposit.cpp b/src/xrpld/app/tx/detail/VaultDeposit.cpp index aeaf8901262..ce81bd0c7f5 100644 --- a/src/xrpld/app/tx/detail/VaultDeposit.cpp +++ b/src/xrpld/app/tx/detail/VaultDeposit.cpp @@ -115,10 +115,7 @@ VaultDeposit::preclaim(PreclaimContext const& ctx) !isTesSuccess(ter)) return ter; - // Asset issuer does not have any balance, they can just create funds by - // depositing in the vault. - if ((vaultAsset.native() || vaultAsset.getIssuer() != account) && - accountHolds( + if (accountSpendable( ctx.view, account, vaultAsset,