Remove conditional LoanDraw code

This commit is contained in:
Ed Hennis
2025-10-01 17:18:16 -04:00
parent 9f1ed7ed61
commit d353f4a2e6
9 changed files with 6 additions and 477 deletions

View File

@@ -563,11 +563,6 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
{sfPreviousPaymentDate, soeREQUIRED},
{sfNextPaymentDueDate, soeREQUIRED},
{sfPaymentRemaining, soeREQUIRED},
//#if LOANDRAW
// TODO: Remove this when you remove the rest of the LOANDRAW blocks.
// Directives don't work with macro expansion.
{sfAssetsAvailable, soeDEFAULT},
//#endif
{sfPrincipalOutstanding, soeREQUIRED},
// Save the original request amount for rounding / scaling of
// other computations, particularly for IOUs

View File

@@ -1065,20 +1065,6 @@ TRANSACTION(ttLOAN_MANAGE, 82, LoanManage,
{sfLoanID, soeREQUIRED},
}))
#if LOANDRAW
/** The Borrower uses this transaction to draws funds from the Loan. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanDraw.h>
#endif
TRANSACTION(ttLOAN_DRAW, 83, LoanDraw,
Delegation::delegatable,
featureLendingProtocol,
noPriv, ({
{sfLoanID, soeREQUIRED},
{sfAmount, soeREQUIRED, soeMPTSupported},
}))
#endif
/** The Borrower uses this transaction to make a Payment on the Loan. */
#if TRANSACTION_INCLUDE
# include <xrpld/app/tx/detail/LoanPay.h>

View File

@@ -86,11 +86,7 @@ class Loan_test : public beast::unit_test::suite
env(del(alice, loanKeylet.key), ter(temDISABLED));
// 3. LoanManage
env(manage(alice, loanKeylet.key, tfLoanImpair), ter(temDISABLED));
#if LOANDRAW && 0
// 4. LoanDraw
env(draw(alice, loanKeylet.key, XRP(500)), ter(temDISABLED));
#endif
// 5. LoanPay
// 4. LoanPay
env(pay(alice, loanKeylet.key, XRP(500)), ter(temDISABLED));
};
failAll(all - featureMPTokensV1);
@@ -237,9 +233,6 @@ class Loan_test : public beast::unit_test::suite
loan->at(sfNextPaymentDueDate) == nextPaymentDate);
env.test.BEAST_EXPECT(
loan->at(sfPaymentRemaining) == paymentRemaining);
#if LOANDRAW
env.test.BEAST_EXPECT(loan->at(sfAssetsAvailable) == 0);
#endif
env.test.BEAST_EXPECT(
loan->at(sfPrincipalRequested) == principalRequested);
env.test.BEAST_EXPECT(
@@ -618,9 +611,6 @@ class Loan_test : public beast::unit_test::suite
BEAST_EXPECT(
loan->at(sfNextPaymentDueDate) == startDate + interval);
BEAST_EXPECT(loan->at(sfPaymentRemaining) == total);
#if LOANDRAW
BEAST_EXPECT(loan->at(sfAssetsAvailable) == beast::zero);
#endif
BEAST_EXPECT(loan->at(sfPrincipalRequested) == principalRequest);
BEAST_EXPECT(loan->at(sfPrincipalOutstanding) == principalRequest);
}
@@ -1258,15 +1248,6 @@ class Loan_test : public beast::unit_test::suite
// defaulted
env.close(nextDueDate + 60s);
#if LOANDRAW && 0
if (impair)
{
// Impaired loans can't be drawn against
env(draw(borrower, loanKeylet.key, broker.asset(100)),
ter(tecNO_PERMISSION));
}
#endif
auto const [amountToBeCovered, brokerAcct] =
getDefaultInfo(state, broker);
@@ -1287,12 +1268,6 @@ class Loan_test : public beast::unit_test::suite
state.principalOutstanding = 0;
verifyLoanStatus(state);
#if LOANDRAW && 0
// Defaulted loans can't be drawn against, either
env(draw(borrower, loanKeylet.key, broker.asset(100)),
ter(tecNO_PERMISSION));
#endif
// Once a loan is defaulted, it can't be managed
env(manage(lender, loanKeylet.key, tfLoanUnimpair),
ter(tecNO_PERMISSION));
@@ -1310,45 +1285,13 @@ class Loan_test : public beast::unit_test::suite
auto state =
getCurrentState(env, broker, loanKeylet, verifyLoanStatus);
BEAST_EXPECT(state.flags == baseFlag);
#if LOANDRAW && 0
auto const borrowerStartingBalance =
env.balance(borrower, broker.asset);
// Try to make a payment before the loan starts
env(pay(borrower, loanKeylet.key, broker.asset(500)),
ter(tecTOO_SOON));
// Advance to the start date of the loan
env.close(state.startDate + 5s);
verifyLoanStatus(state);
// Need to account for fees if the loan is in XRP
PrettyAmount adjustment = broker.asset(0);
if (broker.asset.raw().native())
{
adjustment = 2 * env.current()->fees().base;
}
// Draw the entire available balance
// Need to create the STAmount directly to avoid
// PrettyAsset scaling.
STAmount const drawAmount{broker.asset, state.assetsAvailable};
env(draw(borrower, loanKeylet.key, drawAmount));
#else
STAmount const drawAmount =
STAmount(broker.asset, state.principalRequested - 1);
#endif
env.close(state.startDate + 20s);
auto const loanAge = (env.now() - state.startDate).count();
BEAST_EXPECT(loanAge == 30);
verifyLoanStatus(state);
#if LOANDRAW && 0
BEAST_EXPECT(
env.balance(borrower, broker.asset) ==
borrowerStartingBalance + drawAmount - adjustment);
#endif
// Send some bogus pay transactions
env(pay(borrower,
@@ -1544,64 +1487,8 @@ class Loan_test : public beast::unit_test::suite
broker.brokerID,
broker.asset(coverDepositParameter).number());
#if LOANDRAW && 0
auto const borrowerStartingBalance =
env.balance(borrower, broker.asset);
// Draw the balance
env(draw(
borrower,
keylet::loan(uint256(0)).key,
broker.asset(10)),
ter(temINVALID));
env(draw(borrower, loanKeylet.key, broker.asset(-100)),
ter(temBAD_AMOUNT));
env(draw(borrower, broker.brokerID, broker.asset(100)),
ter(tecNO_ENTRY));
env(draw(evan, loanKeylet.key, broker.asset(500)),
ter(tecNO_PERMISSION));
env(draw(borrower, loanKeylet.key, broker.asset(500)),
ter(tecTOO_SOON));
// Advance to the start date of the loan
env.close(state.startDate + 5s);
env(draw(borrower, loanKeylet.key, broker.asset(10000)),
ter(tecINSUFFICIENT_FUNDS));
{
auto const otherAsset =
broker.asset.raw() == assets[0].raw() ? assets[1]
: assets[0];
env(draw(borrower, loanKeylet.key, otherAsset(100)),
ter(tecWRONG_ASSET));
}
verifyLoanStatus(state);
// Need to account for fees if the loan is in XRP
PrettyAmount adjustment = broker.asset(0);
if (broker.asset.raw().native())
{
adjustment = 5 * env.current()->fees().base;
}
// Draw about half the balance
auto const drawAmount = broker.asset(500);
env(draw(borrower, loanKeylet.key, drawAmount));
state.assetsAvailable -= drawAmount.number();
verifyLoanStatus(state);
BEAST_EXPECT(
env.balance(borrower, broker.asset) ==
borrowerStartingBalance + drawAmount - adjustment);
#endif
// move past the due date + grace period (60s)
env.close(tp{d{state.nextPaymentDate}} + 60s + 20s);
#if LOANDRAW && 0
// Try to draw
env(draw(borrower, loanKeylet.key, broker.asset(100)),
ter(tecNO_PERMISSION));
#endif
auto const [amountToBeCovered, brokerAcct] =
getDefaultInfo(state, broker);
@@ -1624,12 +1511,6 @@ class Loan_test : public beast::unit_test::suite
verifyLoanStatus(state);
#if LOANDRAW && 0
// Same error, different check
env(draw(borrower, loanKeylet.key, broker.asset(100)),
ter(tecNO_PERMISSION));
#endif
// Can't make a payment on it either
env(pay(borrower, loanKeylet.key, broker.asset(300)),
ter(tecKILLED));
@@ -1689,34 +1570,10 @@ class Loan_test : public beast::unit_test::suite
verifyLoanStatus(state);
#if LOANDRAW && 0
auto const borrowerStartingBalance =
env.balance(borrower, broker.asset);
// Need to account for fees if the loan is in XRP
PrettyAmount adjustment = broker.asset(0);
if (broker.asset.raw().native())
{
adjustment = env.current()->fees().base;
}
// Draw the entire available balance
// Need to create the STAmount directly to avoid
// PrettyAsset scaling.
STAmount const drawAmount{broker.asset, state.assetsAvailable};
env(draw(borrower, loanKeylet.key, drawAmount));
#endif
env.close(state.startDate + 20s);
auto const loanAge = (env.now() - state.startDate).count();
BEAST_EXPECT(loanAge == 30);
#if LOANDRAW && 0
verifyLoanStatus(state);
BEAST_EXPECT(
env.balance(borrower, broker.asset) ==
borrowerStartingBalance + drawAmount - adjustment);
#endif
// Periodic payment amount will consist of
// 1. principal outstanding (1000)
// 2. interest interest rate (at 12%)
@@ -2145,11 +2002,6 @@ class Loan_test : public beast::unit_test::suite
env.close(startDate);
#if LOANDRAW && 0
// Draw the loan
env(draw(lender, loanKeylet.key, broker.asset(1000)));
env.close();
#endif
// Make a payment
env(pay(lender, loanKeylet.key, broker.asset(1000)));
}
@@ -2335,23 +2187,6 @@ class Loan_test : public beast::unit_test::suite
BEAST_EXPECT(loan->at(sfPrincipalOutstanding) == actualPrincipal);
}
#if LOANDRAW && 0
auto loanDrawTx =
env.json(draw(borrower, keylet.key, STAmount{broker.asset, Number {
6
}}));
env(loanDrawTx, ter(tesSUCCESS));
env.close();
if (auto const loan = env.le(keylet); BEAST_EXPECT(loan))
{
// Verify the payment decreased the principal
BEAST_EXPECT(loan->at(sfPaymentRemaining) == numPayments);
BEAST_EXPECT(loan->at(sfPrincipalRequested) == actualPrincipal);
BEAST_EXPECT(loan->at(sfPrincipalOutstanding) == actualPrincipal);
}
#endif
auto loanPayTx = env.json(
pay(borrower, keylet.key, STAmount{broker.asset, serviceFee + 6}));
env(loanPayTx, ter(tesSUCCESS));

View File

@@ -835,15 +835,6 @@ manage(AccountID const& account, uint256 const& loanID, std::uint32_t flags);
Json::Value
del(AccountID const& account, uint256 const& loanID, std::uint32_t flags = 0);
#if loandraw
Json::Value
draw(
AccountID const& account,
uint256 const& loanID,
STAmount const& amount,
std::uint32_t flags = 0);
#endif
Json::Value
pay(AccountID const& account,
uint256 const& loanID,

View File

@@ -487,24 +487,6 @@ del(AccountID const& account, uint256 const& loanID, std::uint32_t flags)
return jv;
}
#if LOANDRAW
Json::Value
draw(
AccountID const& account,
uint256 const& loanID,
STAmount const& amount,
std::uint32_t flags)
{
Json::Value jv;
jv[sfTransactionType] = jss::LoanDraw;
jv[sfAccount] = to_string(account);
jv[sfLoanID] = to_string(loanID);
jv[sfAmount] = amount.getJson();
jv[sfFlags] = flags;
return jv;
}
#endif
Json::Value
pay(AccountID const& account,
uint256 const& loanID,

View File

@@ -101,23 +101,6 @@ LoanDelete::doApply()
if (!vaultSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
#if LOANDRAW
// transfer any remaining funds to the borrower
auto const vaultAsset = vaultSle->at(sfAsset);
auto assetsAvailableProxy = loanSle->at(sfAssetsAvailable);
if (assetsAvailableProxy != 0)
{
if (auto const ter = accountSend(
view,
brokerPseudoAccount,
borrower,
STAmount{vaultAsset, assetsAvailableProxy},
j_,
WaiveTransferFee::Yes))
return ter;
}
#endif
// Remove LoanID from Directory of the LoanBroker pseudo-account.
if (!view.dirRemove(
keylet::ownerDir(brokerPseudoAccount),

View File

@@ -1,172 +0,0 @@
#if LOANDRAW
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpld/app/tx/detail/LoanDraw.h>
//
#include <xrpld/app/misc/LendingHelpers.h>
namespace ripple {
bool
LoanDraw::checkExtraFeatures(PreflightContext const& ctx)
{
return checkLendingProtocolDependencies(ctx);
}
NotTEC
LoanDraw::preflight(PreflightContext const& ctx)
{
if (ctx.tx[sfLoanID] == beast::zero)
return temINVALID;
if (ctx.tx[sfAmount] <= beast::zero)
return temBAD_AMOUNT;
return tesSUCCESS;
}
TER
LoanDraw::preclaim(PreclaimContext const& ctx)
{
auto const& tx = ctx.tx;
auto const account = tx[sfAccount];
auto const loanID = tx[sfLoanID];
auto const amount = tx[sfAmount];
auto const loanSle = ctx.view.read(keylet::loan(loanID));
if (!loanSle)
{
JLOG(ctx.j.warn()) << "Loan does not exist.";
return tecNO_ENTRY;
}
if (loanSle->at(sfBorrower) != account)
{
JLOG(ctx.j.warn()) << "Loan does not belong to the account.";
return tecNO_PERMISSION;
}
if (loanSle->isFlag(lsfLoanImpaired) || loanSle->isFlag(lsfLoanDefault))
{
JLOG(ctx.j.warn()) << "Loan is impaired or in default.";
return tecNO_PERMISSION;
}
if (!hasExpired(ctx.view, loanSle->at(sfStartDate)))
{
JLOG(ctx.j.warn()) << "Loan has not started yet.";
return tecTOO_SOON;
}
auto const loanBrokerID = loanSle->at(sfLoanBrokerID);
auto const loanBrokerSle = ctx.view.read(keylet::loanbroker(loanBrokerID));
if (!loanBrokerSle)
{
// This should be impossible
// LCOV_EXCL_START
JLOG(ctx.j.fatal()) << "LoanBroker does not exist.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
auto const brokerPseudoAccount = loanBrokerSle->at(sfAccount);
auto const vaultID = loanBrokerSle->at(sfVaultID);
auto const vaultSle = ctx.view.read(keylet::vault(vaultID));
if (!vaultSle)
{
// This should be impossible
// LCOV_EXCL_START
JLOG(ctx.j.fatal()) << "Vault does not exist.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
auto const asset = vaultSle->at(sfAsset);
if (amount.asset() != asset)
{
JLOG(ctx.j.warn()) << "Draw amount does not match the Vault asset.";
return tecWRONG_ASSET;
}
if (loanSle->at(sfAssetsAvailable) < amount)
{
JLOG(ctx.j.warn()) << "Loan does not have enough assets available.";
return tecINSUFFICIENT_FUNDS;
}
if (auto const ret = checkFrozen(ctx.view, brokerPseudoAccount, asset))
{
JLOG(ctx.j.warn()) << "Loan Broker pseudo-account is frozen.";
return ret;
}
if (auto const ret = checkDeepFrozen(ctx.view, account, asset))
{
JLOG(ctx.j.warn())
<< "Borrower account cannot receive funds (deep frozen).";
return ret;
}
if (hasExpired(ctx.view, loanSle->at(sfNextPaymentDueDate)))
{
JLOG(ctx.j.warn()) << "Loan payment is overdue.";
return tecNO_PERMISSION;
}
return tesSUCCESS;
}
TER
LoanDraw::doApply()
{
auto const& tx = ctx_.tx;
auto& view = ctx_.view();
auto const amount = tx[sfAmount];
auto const loanID = tx[sfLoanID];
auto const loanSle = view.peek(keylet::loan(loanID));
if (!loanSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const brokerID = loanSle->at(sfLoanBrokerID);
auto const brokerSle = view.peek(keylet::loanbroker(brokerID));
if (!brokerSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const brokerPseudoAccount = brokerSle->at(sfAccount);
if (auto const ter = accountSend(
view,
brokerPseudoAccount,
account_,
amount,
j_,
WaiveTransferFee::Yes))
return ter;
loanSle->at(sfAssetsAvailable) -= amount;
view.update(loanSle);
return tesSUCCESS;
}
//------------------------------------------------------------------------------
} // namespace ripple
#endif

View File

@@ -1,55 +0,0 @@
#if LOANDRAW
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_TX_LOANDRAW_H_INCLUDED
#define RIPPLE_TX_LOANDRAW_H_INCLUDED
#include <xrpld/app/tx/detail/Transactor.h>
namespace ripple {
class LoanDraw : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanDraw(ApplyContext& ctx) : Transactor(ctx)
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace ripple
#endif
#endif

View File

@@ -150,15 +150,6 @@ LoanManage::defaultLoan(
auto brokerDebtTotalProxy = brokerSle->at(sfDebtTotal);
auto const totalDefaultAmount = principalOutstanding + interestOutstanding;
#if LOANDRAW
// The default Amount equals the outstanding principal and interest,
// excluding any funds unclaimed by the Borrower.
auto loanAssetsAvailableProxy = loanSle->at(sfAssetsAvailable);
auto const defaultAmount = totalDefaultAmount - loanAssetsAvailableProxy;
#else
// TODO: get rid of this and just use totalDefaultAmount
auto const defaultAmount = totalDefaultAmount;
#endif
// Apply the First-Loss Capital to the Default Amount
TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
TenthBips32 const coverRateLiquidation{
@@ -170,14 +161,10 @@ LoanManage::defaultLoan(
tenthBipsOfValue(
brokerDebtTotalProxy.value(), coverRateMinimum),
coverRateLiquidation),
defaultAmount),
totalDefaultAmount),
originalPrincipalRequested);
#if LOANDRAW
auto const returnToVault = defaultCovered + loanAssetsAvailableProxy;
#else
auto const returnToVault = defaultCovered;
#endif
auto const vaultDefaultAmount = defaultAmount - defaultCovered;
auto const vaultDefaultAmount = totalDefaultAmount - defaultCovered;
// Update the Vault object:
@@ -196,7 +183,7 @@ LoanManage::defaultLoan(
vaultAssetsTotalProxy -= vaultDefaultAmount;
// Increase the Asset Available of the Vault by liquidated First-Loss
// Capital and any unclaimed funds amount:
vaultAssetsAvailableProxy += returnToVault;
vaultAssetsAvailableProxy += defaultCovered;
if (*vaultAssetsAvailableProxy > *vaultAssetsTotalProxy &&
!vaultAsset.native() && vaultAsset.holds<Issue>())
{
@@ -275,9 +262,6 @@ LoanManage::defaultLoan(
// Update the Loan object:
loanSle->setFlag(lsfLoanDefault);
loanSle->at(sfPaymentRemaining) = 0;
#if LOANDRAW
loanAssetsAvailableProxy = 0;
#endif
loanSle->at(sfPrincipalOutstanding) = 0;
view.update(loanSle);
@@ -287,7 +271,7 @@ LoanManage::defaultLoan(
view,
brokerSle->at(sfAccount),
vaultSle->at(sfAccount),
STAmount{vaultAsset, returnToVault},
STAmount{vaultAsset, defaultCovered},
j,
WaiveTransferFee::Yes);
}