mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-28 17:40:25 +00:00
Implement LoanDraw, and made good progress on related tests
This commit is contained in:
@@ -89,11 +89,11 @@ 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 0
|
||||
// 4. LoanDraw
|
||||
env(draw(alice, loanKeylet.key, Number(500)), ter(temDISABLED));
|
||||
env(draw(alice, loanKeylet.key, XRP(500)), ter(temDISABLED));
|
||||
#if 0
|
||||
// 5. LoanPay
|
||||
env(pay(alice, loanKeylet.key, Number(500)), ter(temDISABLED));
|
||||
env(pay(alice, loanKeylet.key, XRP(500)), ter(temDISABLED));
|
||||
#endif
|
||||
};
|
||||
failAll(all - featureMPTokensV1);
|
||||
@@ -112,6 +112,18 @@ class Loan_test : public beast::unit_test::suite
|
||||
}
|
||||
};
|
||||
|
||||
struct LoanState
|
||||
{
|
||||
std::uint32_t previousPaymentDate = 0;
|
||||
NetClock::time_point startDate = {};
|
||||
std::uint32_t nextPaymentDate = 0;
|
||||
std::uint32_t paymentRemaining = 0;
|
||||
Number assetsAvailable = 0;
|
||||
Number principalOutstanding = 0;
|
||||
std::uint32_t flags = 0;
|
||||
std::uint32_t paymentInterval = 0;
|
||||
};
|
||||
|
||||
struct VerifyLoanStatus
|
||||
{
|
||||
public:
|
||||
@@ -184,6 +196,21 @@ class Loan_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
checkBroker(
|
||||
LoanState const& state,
|
||||
TenthBips32 interestRate,
|
||||
std::uint32_t ownerCount) const
|
||||
{
|
||||
checkBroker(
|
||||
state.assetsAvailable,
|
||||
state.principalOutstanding,
|
||||
interestRate,
|
||||
state.paymentInterval,
|
||||
state.paymentRemaining,
|
||||
ownerCount);
|
||||
}
|
||||
|
||||
void
|
||||
operator()(
|
||||
std::uint32_t previousPaymentDate,
|
||||
@@ -251,6 +278,18 @@ class Loan_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
operator()(LoanState const& state) const
|
||||
{
|
||||
operator()(
|
||||
state.previousPaymentDate,
|
||||
state.nextPaymentDate,
|
||||
state.paymentRemaining,
|
||||
state.assetsAvailable,
|
||||
state.principalOutstanding,
|
||||
state.flags);
|
||||
};
|
||||
};
|
||||
|
||||
void
|
||||
@@ -464,7 +503,8 @@ class Loan_test : public beast::unit_test::suite
|
||||
}
|
||||
return Number(0);
|
||||
}();
|
||||
auto const borrowerBalance = env.balance(borrower, broker.asset);
|
||||
auto const borrowerStartingBalance =
|
||||
env.balance(borrower, broker.asset);
|
||||
|
||||
// Try to delete the loan broker with an active loan
|
||||
env(loanBroker::del(lender, broker.brokerID), ter(tecHAS_OBLIGATIONS));
|
||||
@@ -486,7 +526,7 @@ class Loan_test : public beast::unit_test::suite
|
||||
|
||||
BEAST_EXPECT(
|
||||
env.balance(borrower, broker.asset).value() ==
|
||||
borrowerBalance.value() + assetsAvailable);
|
||||
borrowerStartingBalance.value() + assetsAvailable);
|
||||
BEAST_EXPECT(env.ownerCount(borrower) == borrowerOwnerCount);
|
||||
|
||||
if (auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
|
||||
@@ -528,9 +568,12 @@ class Loan_test : public beast::unit_test::suite
|
||||
std::string const iouCurrency{"IOU"};
|
||||
PrettyAsset const iouAsset = issuer[iouCurrency];
|
||||
env(trust(lender, iouAsset(1'000'000)));
|
||||
env(trust(borrower, iouAsset(1'000'000)));
|
||||
env(trust(evan, iouAsset(1'000'000)));
|
||||
env(pay(issuer, evan, iouAsset(100'000)));
|
||||
env(pay(issuer, lender, iouAsset(100'000)));
|
||||
// Fund the borrower with enough to cover interest and fees
|
||||
env(pay(issuer, borrower, iouAsset(1'000)));
|
||||
env.close();
|
||||
|
||||
MPTTester mptt{env, issuer, mptInitNoFund};
|
||||
@@ -538,9 +581,12 @@ class Loan_test : public beast::unit_test::suite
|
||||
{.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
|
||||
PrettyAsset const mptAsset = mptt.issuanceID();
|
||||
mptt.authorize({.account = lender});
|
||||
mptt.authorize({.account = borrower});
|
||||
mptt.authorize({.account = evan});
|
||||
env(pay(issuer, lender, mptAsset(100'000)));
|
||||
env(pay(issuer, evan, mptAsset(100'000)));
|
||||
// Fund the borrower with enough to cover interest and fees
|
||||
env(pay(issuer, borrower, mptAsset(1'000)));
|
||||
env.close();
|
||||
|
||||
std::array const assets{xrpAsset, iouAsset, mptAsset};
|
||||
@@ -606,6 +652,8 @@ class Loan_test : public beast::unit_test::suite
|
||||
|
||||
using namespace loan;
|
||||
using namespace std::chrono_literals;
|
||||
using d = NetClock::duration;
|
||||
using tp = NetClock::time_point;
|
||||
|
||||
Number const principalRequest = broker.asset(1000).value();
|
||||
Number const maxCoveredLoanRequest =
|
||||
@@ -961,6 +1009,38 @@ class Loan_test : public beast::unit_test::suite
|
||||
// Finally! Create a loan
|
||||
std::string testData;
|
||||
|
||||
auto currentState = [&](Keylet const& loanKeylet,
|
||||
VerifyLoanStatus const& verifyLoanStatus) {
|
||||
// Lookup the current loan state
|
||||
LoanState state;
|
||||
if (auto loan = env.le(loanKeylet); BEAST_EXPECT(loan))
|
||||
{
|
||||
state.previousPaymentDate = loan->at(sfPreviousPaymentDate);
|
||||
BEAST_EXPECT(state.previousPaymentDate == 0);
|
||||
state.startDate = tp{d{loan->at(sfStartDate)}};
|
||||
state.nextPaymentDate = loan->at(sfNextPaymentDueDate);
|
||||
BEAST_EXPECT(
|
||||
tp{d{state.nextPaymentDate}} == state.startDate + 600s);
|
||||
state.paymentRemaining = loan->at(sfPaymentRemaining);
|
||||
BEAST_EXPECT(state.paymentRemaining == 12);
|
||||
state.assetsAvailable = loan->at(sfAssetsAvailable);
|
||||
BEAST_EXPECT(
|
||||
state.assetsAvailable == broker.asset(999).value());
|
||||
state.principalOutstanding =
|
||||
loan->at(sfPrincipalOutstanding);
|
||||
BEAST_EXPECT(
|
||||
state.principalOutstanding ==
|
||||
broker.asset(1000).value());
|
||||
state.paymentInterval = loan->at(sfPaymentInterval);
|
||||
BEAST_EXPECT(state.paymentInterval == 600);
|
||||
state.flags = loan->at(sfFlags);
|
||||
}
|
||||
|
||||
verifyLoanStatus(state);
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
auto defaultBeforeStartDate = [&](std::uint32_t baseFlag) {
|
||||
return [&, baseFlag](
|
||||
Keylet const& loanKeylet,
|
||||
@@ -970,64 +1050,22 @@ class Loan_test : public beast::unit_test::suite
|
||||
// Default the loan
|
||||
|
||||
// Initialize values with the current state
|
||||
std::uint32_t previousPaymentDate = 0;
|
||||
std::uint32_t nextPaymentDate = 0;
|
||||
std::uint32_t paymentRemaining = 0;
|
||||
Number assetsAvailable = 0;
|
||||
Number principalOutstanding = 0;
|
||||
std::uint32_t flags = 0;
|
||||
std::uint32_t paymentInterval = 0;
|
||||
|
||||
if (auto loan = env.le(loanKeylet); BEAST_EXPECT(loan))
|
||||
{
|
||||
previousPaymentDate = loan->at(sfPreviousPaymentDate);
|
||||
BEAST_EXPECT(previousPaymentDate == 0);
|
||||
nextPaymentDate = loan->at(sfNextPaymentDueDate);
|
||||
BEAST_EXPECT(
|
||||
nextPaymentDate == loan->at(sfStartDate) + 600);
|
||||
paymentRemaining = loan->at(sfPaymentRemaining);
|
||||
BEAST_EXPECT(paymentRemaining == 12);
|
||||
assetsAvailable = loan->at(sfAssetsAvailable);
|
||||
BEAST_EXPECT(
|
||||
assetsAvailable == broker.asset(999).value());
|
||||
principalOutstanding = loan->at(sfPrincipalOutstanding);
|
||||
BEAST_EXPECT(
|
||||
principalOutstanding == broker.asset(1000).value());
|
||||
paymentInterval = loan->at(sfPaymentInterval);
|
||||
BEAST_EXPECT(paymentInterval == 600);
|
||||
flags = loan->at(sfFlags);
|
||||
BEAST_EXPECT(flags == baseFlag);
|
||||
}
|
||||
|
||||
verifyLoanStatus(
|
||||
previousPaymentDate,
|
||||
nextPaymentDate,
|
||||
paymentRemaining,
|
||||
assetsAvailable,
|
||||
principalOutstanding,
|
||||
flags);
|
||||
auto state = currentState(loanKeylet, verifyLoanStatus);
|
||||
BEAST_EXPECT(state.flags == baseFlag);
|
||||
|
||||
// Impair the loan
|
||||
env(manage(lender, loanKeylet.key, tfLoanImpair));
|
||||
|
||||
flags |= tfLoanImpair;
|
||||
nextPaymentDate = env.now().time_since_epoch().count();
|
||||
verifyLoanStatus(
|
||||
previousPaymentDate,
|
||||
nextPaymentDate,
|
||||
paymentRemaining,
|
||||
assetsAvailable,
|
||||
principalOutstanding,
|
||||
flags);
|
||||
state.flags |= tfLoanImpair;
|
||||
state.nextPaymentDate =
|
||||
env.now().time_since_epoch().count();
|
||||
verifyLoanStatus(state);
|
||||
|
||||
// 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;
|
||||
|
||||
auto const nextDueDate = tp{d{nextPaymentDate}};
|
||||
auto const nextDueDate = tp{d{state.nextPaymentDate}};
|
||||
|
||||
// Can't default the loan yet. The grace period hasn't
|
||||
// expired
|
||||
@@ -1038,20 +1076,22 @@ class Loan_test : public beast::unit_test::suite
|
||||
// defaulted
|
||||
env.close(nextDueDate + 60s);
|
||||
|
||||
// Impaired loans can't be drawn against
|
||||
env(draw(borrower, loanKeylet.key, broker.asset(100)),
|
||||
ter(tecNO_PERMISSION));
|
||||
|
||||
// Default the loan
|
||||
env(manage(lender, loanKeylet.key, tfLoanDefault));
|
||||
|
||||
flags |= tfLoanDefault;
|
||||
paymentRemaining = 0;
|
||||
assetsAvailable = 0;
|
||||
principalOutstanding = 0;
|
||||
verifyLoanStatus(
|
||||
previousPaymentDate,
|
||||
nextPaymentDate,
|
||||
paymentRemaining,
|
||||
assetsAvailable,
|
||||
principalOutstanding,
|
||||
flags);
|
||||
state.flags |= tfLoanDefault;
|
||||
state.paymentRemaining = 0;
|
||||
state.assetsAvailable = 0;
|
||||
state.principalOutstanding = 0;
|
||||
verifyLoanStatus(state);
|
||||
|
||||
// Defaulted loans can't be drawn against, either
|
||||
env(draw(borrower, loanKeylet.key, broker.asset(100)),
|
||||
ter(tecNO_PERMISSION));
|
||||
|
||||
// Once a loan is defaulted, it can't be managed
|
||||
env(manage(lender, loanKeylet.key, tfLoanUnimpair),
|
||||
@@ -1086,9 +1126,8 @@ class Loan_test : public beast::unit_test::suite
|
||||
0,
|
||||
defaultBeforeStartDate(0));
|
||||
|
||||
#if 0
|
||||
lifecycle(
|
||||
"Loan overpayment allowed - Pay off",
|
||||
"Loan overpayment allowed - Draw then default",
|
||||
env,
|
||||
lender,
|
||||
borrower,
|
||||
@@ -1100,13 +1139,74 @@ class Loan_test : public beast::unit_test::suite
|
||||
VerifyLoanStatus const& verifyLoanStatus) {
|
||||
// toEndOfLife
|
||||
//
|
||||
// TODO: Draw and make some payments
|
||||
// Initialize values with the current state
|
||||
auto state = currentState(loanKeylet, verifyLoanStatus);
|
||||
BEAST_EXPECT(state.flags == lsfLoanOverpayment);
|
||||
auto const borrowerStartingBalance =
|
||||
env.balance(borrower, broker.asset);
|
||||
|
||||
// Make payments down to 0
|
||||
// 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));
|
||||
|
||||
// TODO: Try to impair a paid off loan
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// move past the due date
|
||||
env.close(tp{d{state.nextPaymentDate}} + 20s);
|
||||
// Try to draw
|
||||
env(draw(borrower, loanKeylet.key, broker.asset(100)),
|
||||
ter(tecNO_PERMISSION));
|
||||
|
||||
// default the loan
|
||||
env(manage(lender, loanKeylet.key, tfLoanDefault));
|
||||
state.paymentRemaining = 0;
|
||||
state.assetsAvailable = 0;
|
||||
state.principalOutstanding = 0;
|
||||
state.flags |= tfLoanDefault;
|
||||
|
||||
verifyLoanStatus(state);
|
||||
|
||||
// Same error, different check
|
||||
env(draw(borrower, loanKeylet.key, broker.asset(100)),
|
||||
ter(tecNO_PERMISSION));
|
||||
|
||||
// Default
|
||||
});
|
||||
|
||||
#if 0
|
||||
lifecycle(
|
||||
"Loan overpayment prohibited - Pay off",
|
||||
env,
|
||||
|
||||
@@ -749,6 +749,13 @@ 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);
|
||||
|
||||
Json::Value
|
||||
draw(
|
||||
AccountID const& account,
|
||||
uint256 const& loanID,
|
||||
STAmount const& amount,
|
||||
std::uint32_t flags = 0);
|
||||
|
||||
} // namespace loan
|
||||
|
||||
} // namespace jtx
|
||||
|
||||
@@ -513,6 +513,22 @@ del(AccountID const& account, uint256 const& loanID, std::uint32_t flags)
|
||||
return jv;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace loan
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
|
||||
@@ -53,6 +53,9 @@ LoanDelete::isEnabled(PreflightContext const& ctx)
|
||||
NotTEC
|
||||
LoanDelete::doPreflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.tx[sfLoanID] == beast::zero)
|
||||
return temINVALID;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
194
src/xrpld/app/tx/detail/LoanDraw.cpp
Normal file
194
src/xrpld/app/tx/detail/LoanDraw.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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>
|
||||
#include <xrpld/ledger/ApplyView.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STXChainBridge.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
bool
|
||||
LoanDraw::isEnabled(PreflightContext const& ctx)
|
||||
{
|
||||
return LendingProtocolEnabled(ctx);
|
||||
}
|
||||
|
||||
NotTEC
|
||||
LoanDraw::doPreflight(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;
|
||||
}
|
||||
|
||||
if (loanSle->at(sfAssetsAvailable) < amount)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Loan does not have enough assets available.";
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
|
||||
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()) << "Loan amount does not match the Vault asset.";
|
||||
return tecWRONG_ASSET;
|
||||
}
|
||||
|
||||
if (isFrozen(ctx.view, brokerPseudoAccount, asset))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Loan Broker pseudo-account is frozen.";
|
||||
return asset.holds<Issue>() ? tecFROZEN : tecLOCKED;
|
||||
}
|
||||
if (asset.holds<Issue>())
|
||||
{
|
||||
auto const issue = asset.get<Issue>();
|
||||
if (isDeepFrozen(ctx.view, account, issue.currency, issue.account))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Borrower account is frozen.";
|
||||
return tecFROZEN;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
53
src/xrpld/app/tx/detail/LoanDraw.h
Normal file
53
src/xrpld/app/tx/detail/LoanDraw.h
Normal file
@@ -0,0 +1,53 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
isEnabled(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
doPreflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -60,6 +60,9 @@ LoanManage::getFlagsMask(PreflightContext const& ctx)
|
||||
NotTEC
|
||||
LoanManage::doPreflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.tx[sfLoanID] == beast::zero)
|
||||
return temINVALID;
|
||||
|
||||
// Flags are mutually exclusive
|
||||
int numFlags = 0;
|
||||
for (auto const flag : {
|
||||
|
||||
Reference in New Issue
Block a user