Check that the borrower has sufficient funds to make the LoanPay

This commit is contained in:
Ed Hennis
2025-10-22 00:38:05 -04:00
parent 5da586bffc
commit 92144efbdf
2 changed files with 71 additions and 17 deletions

View File

@@ -594,7 +594,7 @@ class Loan_test : public beast::unit_test::suite
{
// Need to account for fees if the loan is in XRP
PrettyAmount adjustment = broker.asset(0);
if (broker.asset.raw().native())
if (broker.asset.native())
{
adjustment = 2 * env.current()->fees().base;
}
@@ -757,7 +757,7 @@ class Loan_test : public beast::unit_test::suite
if (deleter == borrower)
{
// Need to account for fees if the loan is in XRP
if (broker.asset.raw().native())
if (broker.asset.native())
{
adjustment = env.current()->fees().base;
}
@@ -1061,12 +1061,12 @@ class Loan_test : public beast::unit_test::suite
TER> {
// Freeze / lock the asset
std::function<void(Account const& holder)> empty;
if (broker.asset.raw().native())
if (broker.asset.native())
{
// XRP can't be frozen
return std::make_tuple(empty, empty, empty, tesSUCCESS);
}
else if (broker.asset.raw().holds<Issue>())
else if (broker.asset.holds<Issue>())
{
auto freeze = [&](Account const& holder) {
env(trust(issuer, holder[iouCurrency](0), tfSetFreeze));
@@ -1365,15 +1365,24 @@ class Loan_test : public beast::unit_test::suite
// taken
auto const transactionAmount = payoffAmount + broker.asset(10);
// Send a transaction that tries to pay more than the borrowers's
// balance
env(pay(borrower,
loanKeylet.key,
STAmount{
broker.asset,
borrowerBalanceBeforePayment.number() * 2}),
ter(tecINSUFFICIENT_FUNDS));
env(pay(borrower, loanKeylet.key, transactionAmount));
env.close();
// Need to account for fees if the loan is in XRP
PrettyAmount adjustment = broker.asset(0);
if (broker.asset.raw().native())
if (broker.asset.native())
{
adjustment = env.current()->fees().base;
adjustment = env.current()->fees().base * 2;
}
state.paymentRemaining = 0;
@@ -1814,7 +1823,7 @@ class Loan_test : public beast::unit_test::suite
// Need to account for fees if the loan is in XRP
PrettyAmount adjustment = broker.asset(0);
if (broker.asset.raw().native())
if (broker.asset.native())
{
adjustment = env.current()->fees().base;
}
@@ -2730,7 +2739,6 @@ class Loan_test : public beast::unit_test::suite
env(trust(broker, IOU(20'000'000)));
env(pay(issuer, broker, IOU(10'000'000)));
env(trust(borrower, IOU(20'000'000)));
env.close();
auto const brokerInfo = createVaultAndBroker(env, IOU, broker);
@@ -2745,6 +2753,13 @@ class Loan_test : public beast::unit_test::suite
paymentInterval(100),
fee(XRP(100)));
env.close();
env(trust(borrower, IOU(20'000'000)));
// The borrower increases their limit and acquires some IOU so they
// can pay interest
env(pay(issuer, borrower, IOU(500)));
env.close();
if (auto const le = env.le(keylet::loan(keylet.key));
BEAST_EXPECT(le))
{

View File

@@ -22,6 +22,8 @@
#include <xrpld/app/misc/LendingHelpers.h>
#include <xrpld/app/tx/detail/LoanManage.h>
#include <xrpl/json/to_string.h>
namespace ripple {
bool
@@ -203,6 +205,27 @@ LoanPay::preclaim(PreclaimContext const& ctx)
<< "Vault pseudo-account can not receive funds (deep frozen).";
return ret;
}
// Make sure the borrower has enough funds to make the payment!
// Do not support "partial payments" - if the transaction says to pay X,
// then the account must have X available, even if the loan payment takes
// less.
// Also assume that anybody taking loans is not using "community credit",
// which would let an IOU balance go negative up to the other side's limit.
// This may change in a later version.
if (auto const balance = accountHolds(
ctx.view,
account,
asset,
fhZERO_IF_FROZEN,
ahZERO_IF_UNAUTHORIZED,
ctx.j);
balance < amount)
{
JLOG(ctx.j.warn()) << "Payment amount too large. Amount: "
<< to_string(amount.getJson())
<< ". Balance: " << to_string(balance.getJson());
return tecINSUFFICIENT_FUNDS;
}
return tesSUCCESS;
}
@@ -480,20 +503,36 @@ LoanPay::doApply()
: accountHolds(
view, brokerPayee, asset, fhIGNORE_FREEZE, ahIGNORE_AUTH, j_);
/*
auto const balanceScale = std::max(
{accountBalanceBefore.exponent(),
vaultBalanceBefore.exponent(),
brokerBalanceBefore.exponent(),
accountBalanceAfter.exponent(),
vaultBalanceAfter.exponent(),
brokerBalanceAfter.exponent()});
*/
XRPL_ASSERT_PARTS(
accountBalanceBefore + vaultBalanceBefore + brokerBalanceBefore ==
accountBalanceAfter + vaultBalanceAfter + brokerBalanceAfter,
"ripple::LoanPay::doApply",
"funds are conserved (with rounding)");
XRPL_ASSERT_PARTS(
accountBalanceAfter >= beast::zero,
"ripple::LoanPay::doApply",
"positive account balance");
XRPL_ASSERT_PARTS(
accountBalanceAfter < accountBalanceBefore,
"ripple::LoanPay::doApply",
"account balance decreased");
XRPL_ASSERT_PARTS(
vaultBalanceAfter >= beast::zero && brokerBalanceAfter >= beast::zero,
"ripple::LoanPay::doApply",
"positive vault and broker balances");
XRPL_ASSERT_PARTS(
vaultBalanceAfter >= vaultBalanceBefore,
"ripple::LoanPay::doApply",
"vault balance did not decrease");
XRPL_ASSERT_PARTS(
brokerBalanceAfter >= brokerBalanceBefore,
"ripple::LoanPay::doApply",
"broker balance did not decrease");
XRPL_ASSERT_PARTS(
vaultBalanceAfter > vaultBalanceBefore ||
brokerBalanceAfter > brokerBalanceBefore,
"ripple::LoanPay::doApply",
"vault and/or broker balance increased");
#endif
return tesSUCCESS;