Finish LoanManage functionality and tests, modulo LoanDraw/Pay

This commit is contained in:
Ed Hennis
2025-05-04 21:53:07 -04:00
parent ef2a0edc67
commit 5295f47999
5 changed files with 312 additions and 240 deletions

View File

@@ -112,6 +112,47 @@ class Loan_test : public beast::unit_test::suite
}
};
struct VerifyStatus
{
public:
jtx::Env const& env;
BrokerInfo const& broker;
Keylet const& keylet;
VerifyStatus(
jtx::Env const& env_,
BrokerInfo const& broker_,
Keylet const& keylet_)
: env(env_), broker(broker_), keylet(keylet_)
{
}
void
operator()(
std::uint32_t previousPaymentDate,
std::uint32_t nextPaymentDate,
std::uint32_t paymentRemaining,
Number const& assetsAvailable,
Number const& principalOutstanding,
std::uint32_t flags) const
{
if (auto loan = env.le(keylet); env.test.BEAST_EXPECT(loan))
{
env.test.BEAST_EXPECT(
loan->at(sfPreviousPaymentDate) == previousPaymentDate);
env.test.BEAST_EXPECT(
loan->at(sfNextPaymentDueDate) == nextPaymentDate);
env.test.BEAST_EXPECT(
loan->at(sfPaymentRemaining) == paymentRemaining);
env.test.BEAST_EXPECT(
loan->at(sfAssetsAvailable) == assetsAvailable);
env.test.BEAST_EXPECT(
loan->at(sfPrincipalOutstanding) == principalOutstanding);
env.test.BEAST_EXPECT(loan->at(sfFlags) == flags);
}
}
};
void
lifecycle(
const char* label,
@@ -121,10 +162,11 @@ class Loan_test : public beast::unit_test::suite
jtx::Account const& evan,
BrokerInfo const& broker,
std::uint32_t flags,
std::function<jtx::JTx(jtx::JTx const&)> modifyJTx,
std::function<void(SLE::const_ref)> checkLoan,
std::function<void(SLE::const_ref)> changeLoan,
std::function<void(SLE::const_ref)> checkChangedLoan)
// The end of life callback is expected to take the loan to 0 payments
// remaining, one way or another
std::function<
void(Keylet const& loanKeylet, VerifyStatus const& verifyStatus)>
toEndOfLife)
{
auto const [keylet, loanSequence] = [&]() {
auto const brokerSLE = env.le(keylet::loanbroker(broker.brokerID));
@@ -139,6 +181,9 @@ class Loan_test : public beast::unit_test::suite
return std::make_pair(
keylet::loan(broker.brokerID, loanSequence), loanSequence);
}();
VerifyStatus const verifyStatus(env, broker, keylet);
if (!BEAST_EXPECT(loanSequence != 0))
return;
{
@@ -189,14 +234,14 @@ class Loan_test : public beast::unit_test::suite
paymentInterval(interval),
gracePeriod(grace),
fee(loanSetFee));
// Modify as desired
if (modifyJTx)
createJtx = modifyJTx(createJtx);
// Successfully create a Loan
env(createJtx);
env.close();
auto const loanFlags =
createJtx.stx->isFlag(tfLoanOverpayment) ? lsfLoanOverpayment : 0;
if (auto loan = env.le(keylet); BEAST_EXPECT(loan))
{
// log << "loan after create: " << to_string(loan->getJson())
@@ -230,155 +275,59 @@ class Loan_test : public beast::unit_test::suite
loan->at(sfAssetsAvailable) ==
principalRequest - originationFee);
BEAST_EXPECT(loan->at(sfPrincipalOutstanding) == principalRequest);
if (checkLoan)
checkLoan(loan);
}
#if 0
auto verifyStatus = [&env, &broker, &loan, this](
auto previousPaymentDate,
auto nextPaymentDate,
auto paymentRemaining,
auto assetsAvailable,
auto principalOutstanding) {
auto const available = broker.asset(assetsAvailable);
auto const outstanding = broker.asset(principalOutstanding);
BEAST_EXPECT(
loan->at(sfPreviousPaymentDate) == previousPaymentDate);
BEAST_EXPECT(loan->at(sfNextPaymentDueDate) == nextPaymentDate);
BEAST_EXPECT(loan->at(sfPaymentRemaining) == paymentRemaining);
BEAST_EXPECT(loan->at(sfAssetsAvailable) == available.number());
BEAST_EXPECT(
loan->at(sfPrincipalOutstanding) == outstanding.number());
};
verifyStatus(
0,
startDate.time_since_epoch().count() + interval,
total,
principalRequest - originationFee,
principalRequest,
loanFlags | 0);
// Test Cover funding before allowing alterations
env(coverDeposit(alice, uint256(0), vault.asset(10)),
ter(temINVALID));
env(coverDeposit(evan, keylet.key, vault.asset(10)),
ter(tecNO_PERMISSION));
env(coverDeposit(evan, keylet.key, vault.asset(0)),
ter(temBAD_AMOUNT));
env(coverDeposit(evan, keylet.key, vault.asset(-10)),
ter(temBAD_AMOUNT));
env(coverDeposit(alice, vault.vaultID, vault.asset(10)),
ter(tecNO_ENTRY));
// Manage the loan
// no-op
env(manage(lender, keylet.key, 0));
// Only the lender can manage
env(manage(evan, keylet.key, 0), ter(tecNO_PERMISSION));
// unknown flags
env(manage(lender, keylet.key, tfLoanManageMask), ter(temINVALID_FLAG));
// combinations of flags are not allowed
env(manage(lender, keylet.key, tfLoanUnimpair | tfLoanImpair),
ter(temINVALID_FLAG));
env(manage(lender, keylet.key, tfLoanImpair | tfLoanDefault),
ter(temINVALID_FLAG));
env(manage(lender, keylet.key, tfLoanUnimpair | tfLoanDefault),
ter(temINVALID_FLAG));
env(manage(
lender,
keylet.key,
tfLoanUnimpair | tfLoanImpair | tfLoanDefault),
ter(temINVALID_FLAG));
// invalid loan ID
env(manage(lender, broker.brokerID, tfLoanImpair), ter(tecNO_ENTRY));
// Loan is unimpaired, can't unimpair it again
env(manage(lender, keylet.key, tfLoanUnimpair), ter(tecNO_PERMISSION));
// Loan is unimpaired, can't jump straight to default
env(manage(lender, keylet.key, tfLoanDefault), ter(tecNO_PERMISSION));
verifyCoverAmount(0);
// Impair the loan
env(manage(lender, keylet.key, tfLoanImpair));
// Unimpair the loan
env(manage(lender, keylet.key, tfLoanUnimpair));
// Fund the cover deposit
env(coverDeposit(alice, keylet.key, vault.asset(10)));
if (BEAST_EXPECT(broker = env.le(keylet)))
{
verifyCoverAmount(10);
}
env.close();
// Test withdrawal failure cases
env(coverWithdraw(alice, uint256(0), vault.asset(10)),
ter(temINVALID));
env(coverWithdraw(evan, keylet.key, vault.asset(10)),
ter(tecNO_PERMISSION));
env(coverWithdraw(evan, keylet.key, vault.asset(0)),
ter(temBAD_AMOUNT));
env(coverWithdraw(evan, keylet.key, vault.asset(-10)),
ter(temBAD_AMOUNT));
env(coverWithdraw(alice, vault.vaultID, vault.asset(10)),
ter(tecNO_ENTRY));
env(coverWithdraw(alice, keylet.key, vault.asset(900)),
ter(tecINSUFFICIENT_FUNDS));
// TODO: Draw and make some payments
// Withdraw some of the cover amount
env(coverWithdraw(alice, keylet.key, vault.asset(7)));
if (BEAST_EXPECT(broker = env.le(keylet)))
{
verifyCoverAmount(3);
}
if (BEAST_EXPECT(toEndOfLife))
toEndOfLife(keylet, verifyStatus);
// Add some more cover
env(coverDeposit(alice, keylet.key, vault.asset(5)));
if (BEAST_EXPECT(broker = env.le(keylet)))
{
verifyCoverAmount(8);
}
// Withdraw some more
env(coverWithdraw(alice, keylet.key, vault.asset(2)));
if (BEAST_EXPECT(broker = env.le(keylet)))
{
verifyCoverAmount(6);
}
env.close();
// no-op
env(set(alice, vault.vaultID), loanBrokerID(keylet.key));
// Make modifications to the broker
if (changeBroker)
changeBroker(broker);
env.close();
// Check the results of modifications
if (BEAST_EXPECT(broker = env.le(keylet)) && checkChangedBroker)
checkChangedBroker(broker);
// Verify that fields get removed when set to default values
// Debt maximum: explicit 0
// Data: explicit empty
env(set(alice, vault.vaultID),
loanBrokerID(broker->key()),
debtMaximum(Number(0)),
data(""));
// Check the updated fields
if (BEAST_EXPECT(broker = env.le(keylet)))
{
BEAST_EXPECT(!broker->isFieldPresent(sfDebtMaximum));
BEAST_EXPECT(!broker->isFieldPresent(sfData));
}
/////////////////////////////////////
// try to delete the wrong broker object
env(del(alice, vault.vaultID), ter(tecNO_ENTRY));
// evan tries to delete the broker
env(del(evan, keylet.key), ter(tecNO_PERMISSION));
// TODO: test deletion with an active loan
// Note alice's balance of the asset and the broker account's cover
// funds
auto const aliceBalance = env.balance(alice, vault.asset);
auto const coverFunds = env.balance(pseudoAccount, vault.asset);
BEAST_EXPECT(coverFunds.number() == broker->at(sfCoverAvailable));
BEAST_EXPECT(coverFunds != beast::zero);
verifyCoverAmount(6);
// delete the broker
// log << "Broker before delete: " << to_string(broker->getJson())
// << std::endl;
// if (auto const pseudo = env.le(pseudoKeylet);
// BEAST_EXPECT(pseudo))
//{
// log << "Pseudo-account before delete: "
// << to_string(pseudo->getJson()) << std::endl
// << std::endl;
//}
env(del(alice, keylet.key));
env.close();
{
broker = env.le(keylet);
BEAST_EXPECT(!broker);
auto pseudo = env.le(pseudoKeylet);
BEAST_EXPECT(!pseudo);
}
auto const expectedBalance = aliceBalance + coverFunds -
(aliceBalance.value().native()
? STAmount(env.current()->fees().base.value())
: vault.asset(0));
env.require(balance(alice, expectedBalance));
env.require(balance(pseudoAccount, None(vault.asset.raw())));
#endif
// Verify the loan is at EOL
if (auto loan = env.le(keylet); BEAST_EXPECT(loan))
{
BEAST_EXPECT(loan->at(sfPaymentRemaining) == 0);
BEAST_EXPECT(loan->at(sfPrincipalOutstanding) == 0);
}
}
@@ -833,66 +782,125 @@ class Loan_test : public beast::unit_test::suite
// Finally! Create a loan
std::string testData;
auto defaultBeforeStartDate = [&](std::uint32_t baseFlag) {
return [&, baseFlag](
Keylet const& loanKeylet,
VerifyStatus const& verifyStatus) {
// toEndOfLife
//
// Default the loan
// Impair the loan
env(manage(lender, loanKeylet.key, tfLoanImpair));
// Once the loan is impaired, it can't be impaired again
env(manage(lender, loanKeylet.key, tfLoanImpair),
ter(tecNO_PERMISSION));
using d = NetClock::duration;
using tp = NetClock::time_point;
// start time = 3600s
// interval = 600s
// grace period = 60s
auto const nextDueDate = [&]() {
if (auto const loan = env.le(loanKeylet);
BEAST_EXPECT(loan))
{
auto const dueDate =
tp{d{loan->at(sfNextPaymentDueDate)}};
BEAST_EXPECT(dueDate == env.now());
return dueDate;
}
return tp{d{0}};
}();
// Can't default the loan yet. The grace period hasn't
// expired
env(manage(lender, loanKeylet.key, tfLoanDefault),
ter(tecTOO_SOON));
// Let some time pass so that the loan can be
// defaulted
env.close(nextDueDate + 60s);
// Default the loan
env(manage(lender, loanKeylet.key, tfLoanDefault));
// Once a loan is defaulted, it can't be managed
env(manage(lender, loanKeylet.key, tfLoanUnimpair),
ter(tecNO_PERMISSION));
env(manage(lender, loanKeylet.key, tfLoanImpair),
ter(tecNO_PERMISSION));
verifyStatus(
0,
nextDueDate.time_since_epoch().count(),
0,
0,
0,
baseFlag | tfLoanDefault);
};
};
// There are a lot of fields that can be set on a loan, but most of
// them only affect the "math" when a payment is made. The only one
// that really affects behavior is the `tfLoanOverpayment` flag.
lifecycle(
"Loan overpayment allowed",
"Loan overpayment allowed - Default before start date",
env,
lender,
borrower,
evan,
broker,
tfLoanOverpayment,
{},
[&](SLE::const_ref broker) {
// Extra checks
},
[&](SLE::const_ref broker) {
// Modifications
},
[&](SLE::const_ref broker) {
// Check the updated fields
defaultBeforeStartDate(lsfLoanOverpayment));
#if 0
lifecycle(
"Loan overpayment allowed - Pay off",
env,
lender,
borrower,
evan,
broker,
tfLoanOverpayment,
[&](Keylet const& loanKeylet,
VerifyStatus const& verifyStatus) {
// toEndOfLife
//
// Make payments down to 0
// TODO: Try to impair a paid off loan
});
#endif
lifecycle(
"Loan overpayment prohibited - Default before start date",
env,
lender,
borrower,
evan,
broker,
0,
defaultBeforeStartDate(0));
#if 0
lifecycle(
"non-default fields",
"Loan overpayment prohibited - Pay off",
env,
lender,
borrower,
evan,
vault,
[&](jtx::JTx const& jv) {
testData = "spam spam spam spam";
// Finally, create another Loan Broker with none of the
// values at default
return env.jt(
jv,
data(testData),
managementFeeRate(TenthBips16(123)),
debtMaximum(Number(9)),
coverRateMinimum(TenthBips32(100)),
coverRateLiquidation(TenthBips32(200)));
},
[&](SLE::const_ref broker) {
// Extra checks
BEAST_EXPECT(broker->at(sfManagementFeeRate) == 123);
BEAST_EXPECT(broker->at(sfCoverRateMinimum) == 100);
BEAST_EXPECT(broker->at(sfCoverRateLiquidation) == 200);
BEAST_EXPECT(broker->at(sfDebtMaximum) == Number(9));
BEAST_EXPECT(checkVL(broker->at(sfData), testData));
},
[&](SLE::const_ref broker) {
// Reset Data & Debt maximum to default values
env(set(lender, broker.brokerID),
loanBrokerID(broker->key()),
data(""),
debtMaximum(Number(0)));
},
[&](SLE::const_ref broker) {
// Check the updated fields
BEAST_EXPECT(!broker->isFieldPresent(sfData));
BEAST_EXPECT(!broker->isFieldPresent(sfDebtMaximum));
broker,
0,
[&](Keylet const& loanKeylet,
VerifyStatus const& verifyStatus) {
// toEndOfLife
//
// Make payments down to 0
// TODO: Try to impair a paid off loan
});
#endif
}

View File

@@ -662,25 +662,27 @@ create(
namespace loanBroker {
Json::Value
set(AccountID const& account, uint256 const& vaultId, uint32_t flags = 0);
set(AccountID const& account, uint256 const& vaultId, std::uint32_t flags = 0);
// Use "del" because "delete" is a reserved word in C++.
Json::Value
del(AccountID const& account, uint256 const& loanBrokerID, uint32_t flags = 0);
del(AccountID const& account,
uint256 const& loanBrokerID,
std::uint32_t flags = 0);
Json::Value
coverDeposit(
AccountID const& account,
uint256 const& loanBrokerID,
STAmount const& amount,
uint32_t flags = 0);
std::uint32_t flags = 0);
Json::Value
coverWithdraw(
AccountID const& account,
uint256 const& loanBrokerID,
STAmount const& amount,
uint32_t flags = 0);
std::uint32_t flags = 0);
auto const loanBrokerID = JTxFieldWrapper<uint256Field>(sfLoanBrokerID);
@@ -703,10 +705,10 @@ namespace loan {
Json::Value
set(AccountID const& account,
uint256 loanBrokerID,
uint256 const& loanBrokerID,
Number principalRequested,
NetClock::time_point const& startDate,
uint32_t flags = 0);
std::uint32_t flags = 0);
auto const counterparty = JTxFieldWrapper<accountIDField>(sfCounterparty);
@@ -740,6 +742,10 @@ auto const paymentTotal = simpleField<SF_UINT32>(sfPaymentTotal);
auto const paymentInterval = simpleField<SF_UINT32>(sfPaymentInterval);
auto const gracePeriod = simpleField<SF_UINT32>(sfGracePeriod);
Json::Value
manage(AccountID const& account, uint256 const& loanID, std::uint32_t flags);
} // namespace loan
} // namespace jtx

View File

@@ -476,10 +476,10 @@ namespace loan {
Json::Value
set(AccountID const& account,
uint256 loanBrokerID,
uint256 const& loanBrokerID,
Number principalRequested,
NetClock::time_point const& startDate,
uint32_t flags)
std::uint32_t flags)
{
Json::Value jv;
jv[sfTransactionType] = jss::LoanSet;
@@ -491,6 +491,17 @@ set(AccountID const& account,
return jv;
}
Json::Value
manage(AccountID const& account, uint256 const& loanID, std::uint32_t flags)
{
Json::Value jv;
jv[sfTransactionType] = jss::LoanManage;
jv[sfAccount] = to_string(account);
jv[sfLoanID] = to_string(loanID);
jv[sfFlags] = flags;
return jv;
}
} // namespace loan
} // namespace jtx
} // namespace test

View File

@@ -1914,7 +1914,8 @@ ValidLoanBroker::finalize(
ReadView const& view,
beast::Journal const& j)
{
bool const enforce = view.rules().enabled(featureLendingProtocol);
// Loan Brokers will not exist on ledger if the Lending Protocol amendment
// is not enabled, so there's no need to check it.
for (auto const& [before, after] : brokers_)
{
@@ -1929,12 +1930,7 @@ ValidLoanBroker::finalize(
{
if (!goodZeroDirectory(view, dir, j))
{
XRPL_ASSERT(
enforce,
"ripple::ValidLoanBroker::finalize : Enforcing "
"invariant: directory");
if (enforce)
return false;
return false;
}
}
}
@@ -1942,12 +1938,7 @@ ValidLoanBroker::finalize(
{
JLOG(j.fatal()) << "Invariant failed: Loan Broker sequence number "
"decreased";
XRPL_ASSERT(
enforce,
"ripple::ValidLoanBroker::finalize : Enforcing "
"invariant: loan sequence");
if (enforce)
return false;
return false;
}
}
return true;
@@ -1975,7 +1966,8 @@ ValidLoan::finalize(
ReadView const& view,
beast::Journal const& j)
{
bool const enforce = view.rules().enabled(featureLendingProtocol);
// Loan Brokers will not exist on ledger if the Lending Protocol amendment
// is not enabled, so there's no need to check it.
for (auto const& [before, after] : loans_)
{
@@ -1984,12 +1976,15 @@ ValidLoan::finalize(
if (after->at(sfPaymentRemaining) == 0 &&
after->at(sfPrincipalOutstanding) != 0)
{
XRPL_ASSERT(
enforce,
"ripple::ValidLoan::finalize : Enforcing "
"invariant: zero payments remaining");
if (enforce)
return false;
return false;
}
if (before &&
(before->isFlag(lsfLoanOverpayment) !=
after->isFlag(lsfLoanOverpayment)))
{
JLOG(j.fatal())
<< "Invariant failed: Loan Overpayment flag changed";
return false;
}
}
return true;

View File

@@ -100,7 +100,6 @@ LoanManage::preclaim(PreclaimContext const& ctx)
// 2. It can get worse: unimpaired -> impaired -> default
// 3. It can get better: impaired -> unimpaired
// 4. If it's in a state, it can't be put in that state again.
// TODO: implement this.
if (loanSle->isFlag(lsfLoanDefault))
{
JLOG(ctx.j.warn())
@@ -113,6 +112,14 @@ LoanManage::preclaim(PreclaimContext const& ctx)
<< "Loan is impaired. A loan can not be impaired twice.";
return tecNO_PERMISSION;
}
if (!(loanSle->isFlag(lsfLoanImpaired) ||
loanSle->isFlag(lsfLoanDefault)) &&
(tx.isFlag(tfLoanDefault) || tx.isFlag(tfLoanUnimpair)))
{
JLOG(ctx.j.warn())
<< "Loan is unimpaired. Only valid modification is to impair";
return tecNO_PERMISSION;
}
if (loanSle->at(sfPaymentRemaining) == 0)
{
JLOG(ctx.j.warn()) << "Loan is fully paid. A loan can not be modified "
@@ -155,9 +162,63 @@ defaultLoan(
SLE::ref vaultSle,
Number const& principalOutstanding,
Number const& interestOutstanding,
Asset const& vaultAsset,
beast::Journal j)
{
return temDISABLED;
// Calculate the amount of the Default that First-Loss Capital covers:
// The default Amount equals the outstanding principal and interest,
// excluding any funds unclaimed by the Borrower.
auto assetsAvailableProxy = loanSle->at(sfAssetsAvailable);
auto defaultAmount =
(principalOutstanding + interestOutstanding) - assetsAvailableProxy;
// Apply the First-Loss Capital to the Default Amount
auto debtTotalProxy = brokerSle->at(sfDebtTotal);
TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
TenthBips32 const coverRateLiquidation{
brokerSle->at(sfCoverRateLiquidation)};
auto const defaultCovered = std::min(
tenthBipsOfValue(
tenthBipsOfValue(debtTotalProxy.value(), coverRateMinimum),
coverRateLiquidation),
defaultAmount);
defaultAmount -= defaultCovered;
// Update the Vault object:
// Decrease the Total Value of the Vault:
vaultSle->at(sfAssetsTotal) -= defaultAmount;
// Increase the Asset Available of the Vault by liquidated First-Loss
// Capital and any unclaimed funds amount:
vaultSle->at(sfAssetsAvailable) += defaultCovered + assetsAvailableProxy;
view.update(vaultSle);
// Update the LoanBroker object:
// Decrease the Debt of the LoanBroker:
debtTotalProxy -=
principalOutstanding + interestOutstanding + assetsAvailableProxy;
// Decrease the First-Loss Capital Cover Available:
brokerSle->at(sfCoverAvailable) -= defaultCovered;
view.update(brokerSle);
// Update the Loan object:
loanSle->setFlag(lsfLoanDefault);
loanSle->clearFlag(lsfLoanImpaired);
loanSle->at(sfPaymentRemaining) = 0;
assetsAvailableProxy = 0;
loanSle->at(sfPrincipalOutstanding) = 0;
view.update(loanSle);
// Move the First-Loss Capital from the LoanBroker pseudo-account to the
// Vault pseudo-account:
return accountSend(
view,
brokerSle->at(sfAccount),
vaultSle->at(sfAccount),
STAmount{vaultAsset, defaultCovered},
j,
WaiveTransferFee::Yes);
}
TER
@@ -168,6 +229,7 @@ impairLoan(
SLE::ref vaultSle,
Number const& principalOutstanding,
Number const& interestOutstanding,
Asset const& vaultAsset,
beast::Journal j)
{
// Update the Vault object(set "paper loss")
@@ -176,7 +238,7 @@ impairLoan(
view.update(vaultSle);
// Update the Loan object
loanSle->at(sfFlags) = lsfLoanImpaired;
loanSle->setFlag(lsfLoanImpaired);
auto nextDueProxy = loanSle->at(sfNextPaymentDueDate);
if (!hasExpired(view, nextDueProxy))
{
@@ -197,6 +259,7 @@ unimpairLoan(
SLE::ref vaultSle,
Number const& principalOutstanding,
Number const& interestOutstanding,
Asset const& vaultAsset,
beast::Journal j)
{
// Update the Vault object(clear "paper loss")
@@ -205,7 +268,7 @@ unimpairLoan(
view.update(vaultSle);
// Update the Loan object
loanSle->at(sfFlags) = 0;
loanSle->clearFlag(lsfLoanImpaired);
auto const paymentInterval = loanSle->at(sfPaymentInterval);
auto const normalPaymentDueDate =
loanSle->at(sfPreviousPaymentDate) + paymentInterval;
@@ -250,9 +313,9 @@ LoanManage::doApply()
TenthBips32 const interestRate{loanSle->at(sfInterestRate)};
auto const principalOutstanding = loanSle->at(sfPrincipalOutstanding);
auto const interestOutstanding =
tenthBipsOfValue(principalOutstanding, interestRate);
tenthBipsOfValue(principalOutstanding.value(), interestRate);
// Valid flag combinations are checked in preflight. Not flags is valid -
// Valid flag combinations are checked in preflight. No flags is valid -
// just a noop.
if (tx.isFlag(tfLoanDefault))
{
@@ -263,6 +326,7 @@ LoanManage::doApply()
vaultSle,
principalOutstanding,
interestOutstanding,
vaultAsset,
j_))
return ter;
}
@@ -275,6 +339,7 @@ LoanManage::doApply()
vaultSle,
principalOutstanding,
interestOutstanding,
vaultAsset,
j_))
return ter;
}
@@ -287,23 +352,10 @@ LoanManage::doApply()
vaultSle,
principalOutstanding,
interestOutstanding,
vaultAsset,
j_))
return ter;
}
/*
auto const brokerOwner = brokerSle->at(sfOwner);
auto const brokerOwnerSle = view.peek(keylet::account(brokerOwner));
auto const vaultPseudo = vaultSle->at(sfAccount);
auto const brokerPseudo = brokerSle->at(sfAccount);
auto const brokerPseudoSle = view.peek(keylet::account(brokerPseudo));
auto const principalRequested = tx[sfPrincipalRequested];
TenthBips32 const interestRate{tx[~sfInterestRate].value_or(0)};
auto const originationFee = tx[~sfLoanOriginationFee];
auto const loanAssetsAvailable =
principalRequested - originationFee.value_or(Number{});
*/
return tesSUCCESS;
}