mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-28 17:40:25 +00:00
sync with target
This commit is contained in:
@@ -1491,30 +1491,100 @@ class LoanBroker_test : public beast::unit_test::suite
|
||||
{
|
||||
BEAST_EXPECT(broker->at(sfCoverAvailable) == asset(10).number());
|
||||
}
|
||||
}
|
||||
testLoanBrokerSetDebtMaximum()
|
||||
{
|
||||
testcase("testLoanBrokerSetDebtMaximum");
|
||||
using namespace jtx;
|
||||
using namespace loanBroker;
|
||||
Account const issuer{"issuer"};
|
||||
Account const alice{"alice"};
|
||||
Env env(*this);
|
||||
Vault vault{env};
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testLoanBrokerCoverDepositNullVault();
|
||||
env.fund(XRP(100'000), issuer, alice);
|
||||
env.close();
|
||||
|
||||
testDisabled();
|
||||
testLifecycle();
|
||||
testInvalidLoanBrokerCoverClawback();
|
||||
testInvalidLoanBrokerCoverDeposit();
|
||||
testInvalidLoanBrokerCoverWithdraw();
|
||||
testInvalidLoanBrokerDelete();
|
||||
testInvalidLoanBrokerSet();
|
||||
testRequireAuth();
|
||||
testIssuerCoverDepositDuringGlobalFreeze();
|
||||
PrettyAsset const asset = [&]() {
|
||||
env(trust(alice, issuer["IOU"](1'000'000)), THISLINE);
|
||||
env.close();
|
||||
return PrettyAsset(issuer["IOU"]);
|
||||
}();
|
||||
|
||||
// TODO: Write clawback failure tests with an issuer / MPT that doesn't
|
||||
// have the right flags set.
|
||||
}
|
||||
};
|
||||
env(pay(issuer, alice, asset(100'000)), THISLINE);
|
||||
env.close();
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(LoanBroker, tx, xrpl);
|
||||
auto [tx, vaultKeylet] =
|
||||
vault.create({.owner = alice, .asset = asset});
|
||||
env(tx, THISLINE);
|
||||
env.close();
|
||||
auto const le = env.le(vaultKeylet);
|
||||
VaultInfo vaultInfo = [&]() {
|
||||
if (BEAST_EXPECT(le))
|
||||
return VaultInfo{asset, vaultKeylet.key, le->at(sfAccount)};
|
||||
return VaultInfo{asset, {}, {}};
|
||||
}();
|
||||
if (vaultInfo.vaultID == uint256{})
|
||||
return;
|
||||
|
||||
env(vault.deposit(
|
||||
{.depositor = alice,
|
||||
.id = vaultKeylet.key,
|
||||
.amount = asset(50)}),
|
||||
THISLINE);
|
||||
env.close();
|
||||
|
||||
auto const brokerKeylet =
|
||||
keylet::loanbroker(alice.id(), env.seq(alice));
|
||||
env(set(alice, vaultInfo.vaultID), THISLINE);
|
||||
env.close();
|
||||
|
||||
Account const borrower{"borrower"};
|
||||
env.fund(XRP(1'000), borrower);
|
||||
env(loan::set(borrower, brokerKeylet.key, asset(50).value()),
|
||||
sig(sfCounterpartySignature, alice),
|
||||
fee(env.current()->fees().base * 2),
|
||||
THISLINE);
|
||||
auto const broker = env.le(brokerKeylet);
|
||||
if (!BEAST_EXPECT(broker))
|
||||
return;
|
||||
|
||||
BEAST_EXPECT(broker->at(sfDebtTotal) == 50);
|
||||
auto debtTotal = broker->at(sfDebtTotal);
|
||||
|
||||
auto tx2 = set(alice, vaultInfo.vaultID);
|
||||
tx2[sfLoanBrokerID] = to_string(brokerKeylet.key);
|
||||
tx2[sfDebtMaximum] = debtTotal - 1;
|
||||
env(tx2, ter(tecLIMIT_EXCEEDED), THISLINE);
|
||||
|
||||
tx2[sfDebtMaximum] = debtTotal + 1;
|
||||
env(tx2, ter(tesSUCCESS), THISLINE);
|
||||
|
||||
tx2[sfDebtMaximum] = 0;
|
||||
env(tx2, ter(tesSUCCESS), THISLINE);
|
||||
}
|
||||
|
||||
public:
|
||||
void run() override
|
||||
{
|
||||
testLoanBrokerSetDebtMaximum();
|
||||
testLoanBrokerCoverDepositNullVault();
|
||||
|
||||
testDisabled();
|
||||
testLifecycle();
|
||||
testInvalidLoanBrokerCoverClawback();
|
||||
testInvalidLoanBrokerCoverDeposit();
|
||||
testInvalidLoanBrokerCoverWithdraw();
|
||||
testInvalidLoanBrokerDelete();
|
||||
testInvalidLoanBrokerSet();
|
||||
testRequireAuth();
|
||||
testIssuerCoverDepositDuringGlobalFreeze();
|
||||
|
||||
// TODO: Write clawback failure tests with an issuer / MPT that
|
||||
// doesn't have the right flags set.
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(LoanBroker, tx, xrpl);
|
||||
|
||||
} // namespace test
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -592,7 +592,7 @@ protected:
|
||||
auto const unrealizedLoss = vaultSle->at(sfLossUnrealized) +
|
||||
state.totalValue - state.managementFeeOutstanding;
|
||||
|
||||
if (unrealizedLoss > assetsUnavailable)
|
||||
if (!BEAST_EXPECT(unrealizedLoss <= assetsUnavailable))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -3464,11 +3464,12 @@ protected:
|
||||
ter{tecNO_AUTH});
|
||||
env.close();
|
||||
|
||||
// Can create loan without origination fee
|
||||
// Cannot create loan, even without an origination fee
|
||||
env(set(borrower, broker.brokerID, principalRequest),
|
||||
counterparty(lender),
|
||||
sig(sfCounterpartySignature, lender),
|
||||
fee(env.current()->fees().base * 5));
|
||||
fee(env.current()->fees().base * 5),
|
||||
ter{tecNO_AUTH});
|
||||
env.close();
|
||||
|
||||
// No MPToken for lender - no authorization and no payment
|
||||
@@ -7038,6 +7039,350 @@ protected:
|
||||
paymentParams);
|
||||
}
|
||||
|
||||
void
|
||||
testLoanPayBrokerOwnerMissingTrustline()
|
||||
{
|
||||
testcase << "LoanPay Broker Owner Missing Trustline (PoC)";
|
||||
using namespace jtx;
|
||||
using namespace loan;
|
||||
Account const issuer("issuer");
|
||||
Account const borrower("borrower");
|
||||
Account const broker("broker");
|
||||
auto const IOU = issuer["IOU"];
|
||||
Env env(*this, all);
|
||||
env.fund(XRP(20'000), issuer, broker, borrower);
|
||||
env.close();
|
||||
// Set up trustlines and fund accounts
|
||||
env(trust(broker, IOU(20'000'000)));
|
||||
env(trust(borrower, IOU(20'000'000)));
|
||||
env(pay(issuer, broker, IOU(10'000'000)));
|
||||
env(pay(issuer, borrower, IOU(1'000)));
|
||||
env.close();
|
||||
// Create vault and broker
|
||||
auto const brokerInfo = createVaultAndBroker(env, IOU, broker);
|
||||
// Create a loan first (this creates debt)
|
||||
auto const keylet = keylet::loan(brokerInfo.brokerID, 1);
|
||||
env(set(borrower, brokerInfo.brokerID, 10'000),
|
||||
sig(sfCounterpartySignature, broker),
|
||||
loanServiceFee(IOU(100).value()),
|
||||
paymentInterval(100),
|
||||
fee(XRP(100)));
|
||||
env.close();
|
||||
// Ensure broker has sufficient cover so brokerPayee == brokerOwner
|
||||
// We need coverAvailable >= (debtTotal * coverRateMinimum)
|
||||
// Deposit enough cover to ensure the fee goes to broker owner
|
||||
// The default coverRateMinimum is 10%, so for a 10,000 loan we need
|
||||
// at least 1,000 cover. Default cover is 1,000, so we add more to be
|
||||
// safe.
|
||||
auto const additionalCover = IOU(50'000).value();
|
||||
env(loanBroker::coverDeposit(
|
||||
broker, brokerInfo.brokerID, STAmount{IOU, additionalCover}));
|
||||
env.close();
|
||||
// Verify broker owner has a trustline
|
||||
auto const brokerTrustline = keylet::line(broker, IOU);
|
||||
BEAST_EXPECT(env.le(brokerTrustline) != nullptr);
|
||||
// Broker owner deletes their trustline
|
||||
// First, pay any positive balance to issuer to zero it out
|
||||
auto const brokerBalance = env.balance(broker, IOU);
|
||||
env(pay(broker, issuer, brokerBalance));
|
||||
env.close();
|
||||
// Remove the trustline by setting limit to 0
|
||||
env(trust(broker, IOU(0)));
|
||||
env.close();
|
||||
// Verify trustline is deleted
|
||||
BEAST_EXPECT(env.le(brokerTrustline) == nullptr);
|
||||
// Now borrower tries to make a payment
|
||||
// We should get a tesSUCCESS instead of a tecNO_LINE.
|
||||
env(pay(borrower, keylet.key, IOU(10'100)),
|
||||
fee(XRP(100)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
// Verify trustline is still deleted
|
||||
BEAST_EXPECT(env.le(brokerTrustline) == nullptr);
|
||||
// Verify the service fee went to the broker pseudo-account
|
||||
if (auto const brokerSle =
|
||||
env.le(keylet::loanbroker(brokerInfo.brokerID));
|
||||
BEAST_EXPECT(brokerSle))
|
||||
{
|
||||
Account const pseudo("pseudo-account", brokerSle->at(sfAccount));
|
||||
auto const balance = env.balance(pseudo, IOU);
|
||||
// 1,000 default + 50,000 extra + 100 service fee from LoanPay
|
||||
BEAST_EXPECTS(
|
||||
balance == IOU(51'100), to_string(Json::Value(balance)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testLoanPayBrokerOwnerUnauthorizedMPT()
|
||||
{
|
||||
testcase << "LoanPay Broker Owner MPT unauthorized";
|
||||
using namespace jtx;
|
||||
using namespace loan;
|
||||
|
||||
Account const issuer("issuer");
|
||||
Account const borrower("borrower");
|
||||
Account const broker("broker");
|
||||
|
||||
Env env(*this, all);
|
||||
env.fund(XRP(20'000), issuer, broker, borrower);
|
||||
env.close();
|
||||
|
||||
MPTTester mptt{env, issuer, mptInitNoFund};
|
||||
mptt.create(
|
||||
{.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
|
||||
|
||||
PrettyAsset const MPT{mptt.issuanceID()};
|
||||
|
||||
// Authorize broker and borrower
|
||||
mptt.authorize({.account = broker});
|
||||
mptt.authorize({.account = borrower});
|
||||
|
||||
env.close();
|
||||
|
||||
// Fund accounts
|
||||
env(pay(issuer, broker, MPT(10'000'000)));
|
||||
env(pay(issuer, borrower, MPT(1'000)));
|
||||
env.close();
|
||||
|
||||
// Create vault and broker
|
||||
auto const brokerInfo = createVaultAndBroker(env, MPT, broker);
|
||||
// Create a loan first (this creates debt)
|
||||
auto const keylet = keylet::loan(brokerInfo.brokerID, 1);
|
||||
env(set(borrower, brokerInfo.brokerID, 10'000),
|
||||
sig(sfCounterpartySignature, broker),
|
||||
loanServiceFee(MPT(100).value()),
|
||||
paymentInterval(100),
|
||||
fee(XRP(100)));
|
||||
env.close();
|
||||
// Ensure broker has sufficient cover so brokerPayee == brokerOwner
|
||||
// We need coverAvailable >= (debtTotal * coverRateMinimum)
|
||||
// Deposit enough cover to ensure the fee goes to broker owner
|
||||
// The default coverRateMinimum is 10%, so for a 10,000 loan we need
|
||||
// at least 1,000 cover. Default cover is 1,000, so we add more to be
|
||||
// safe.
|
||||
auto const additionalCover = MPT(50'000).value();
|
||||
env(loanBroker::coverDeposit(
|
||||
broker, brokerInfo.brokerID, STAmount{MPT, additionalCover}));
|
||||
env.close();
|
||||
// Verify broker owner is authorized
|
||||
auto const brokerMpt = keylet::mptoken(mptt.issuanceID(), broker);
|
||||
BEAST_EXPECT(env.le(brokerMpt) != nullptr);
|
||||
// Broker owner unauthorizes.
|
||||
// First, pay any positive balance to issuer to zero it out
|
||||
auto const brokerBalance = env.balance(broker, MPT);
|
||||
env(pay(broker, issuer, brokerBalance));
|
||||
env.close();
|
||||
// Then, unauthorize the MPT.
|
||||
mptt.authorize({.account = broker, .flags = tfMPTUnauthorize});
|
||||
env.close();
|
||||
// Verify the MPT is unauthorized.
|
||||
BEAST_EXPECT(env.le(brokerMpt) == nullptr);
|
||||
// Now borrower tries to make a payment
|
||||
// We should get a tesSUCCESS instead of a tecNO_AUTH.
|
||||
auto const borrowerBalance = env.balance(borrower, MPT);
|
||||
env(pay(borrower, keylet.key, MPT(10'100)),
|
||||
fee(XRP(100)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
// Verify the MPT is still unauthorized.
|
||||
BEAST_EXPECT(env.le(brokerMpt) == nullptr);
|
||||
// Verify the service fee went to the broker pseudo-account
|
||||
if (auto const brokerSle =
|
||||
env.le(keylet::loanbroker(brokerInfo.brokerID));
|
||||
BEAST_EXPECT(brokerSle))
|
||||
{
|
||||
Account const pseudo("pseudo-account", brokerSle->at(sfAccount));
|
||||
auto const balance = env.balance(pseudo, MPT);
|
||||
// 1,000 default + 50,000 extra + 100 service fee from LoanPay
|
||||
BEAST_EXPECTS(
|
||||
balance == MPT(51'100), to_string(Json::Value(balance)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testLoanPayBrokerOwnerNoPermissionedDomainMPT()
|
||||
{
|
||||
testcase
|
||||
<< "LoanPay Broker Owner without permissioned domain of the MPT";
|
||||
using namespace jtx;
|
||||
using namespace loan;
|
||||
|
||||
Account const issuer("issuer");
|
||||
Account const borrower("borrower");
|
||||
Account const broker("broker");
|
||||
|
||||
Env env(*this, all);
|
||||
env.fund(XRP(20'000), issuer, broker, borrower);
|
||||
env.close();
|
||||
|
||||
auto credType = "credential1";
|
||||
|
||||
pdomain::Credentials const credentials1{{issuer, credType}};
|
||||
env(pdomain::setTx(issuer, credentials1));
|
||||
env.close();
|
||||
|
||||
auto domainID = pdomain::getNewDomain(env.meta());
|
||||
|
||||
env(credentials::create(broker, issuer, credType));
|
||||
env(credentials::accept(broker, issuer, credType));
|
||||
env.close();
|
||||
|
||||
env(credentials::create(borrower, issuer, credType));
|
||||
env(credentials::accept(borrower, issuer, credType));
|
||||
env.close();
|
||||
|
||||
MPTTester mptt{env, issuer, mptInitNoFund};
|
||||
mptt.create({
|
||||
.flags = tfMPTCanClawback | tfMPTRequireAuth | tfMPTCanTransfer |
|
||||
tfMPTCanLock,
|
||||
.domainID = domainID,
|
||||
});
|
||||
|
||||
PrettyAsset const MPT{mptt.issuanceID()};
|
||||
|
||||
// Authorize broker and borrower
|
||||
mptt.authorize({.account = broker});
|
||||
mptt.authorize({.account = borrower});
|
||||
|
||||
env.close();
|
||||
|
||||
// Fund accounts
|
||||
env(pay(issuer, broker, MPT(10'000'000)));
|
||||
env(pay(issuer, borrower, MPT(1'000)));
|
||||
env.close();
|
||||
|
||||
// Create vault and broker
|
||||
auto const brokerInfo = createVaultAndBroker(env, MPT, broker);
|
||||
// Create a loan first (this creates debt)
|
||||
auto const keylet = keylet::loan(brokerInfo.brokerID, 1);
|
||||
env(set(borrower, brokerInfo.brokerID, 10'000),
|
||||
sig(sfCounterpartySignature, broker),
|
||||
loanServiceFee(MPT(100).value()),
|
||||
paymentInterval(100),
|
||||
fee(XRP(100)));
|
||||
env.close();
|
||||
// Ensure broker has sufficient cover so brokerPayee == brokerOwner
|
||||
// We need coverAvailable >= (debtTotal * coverRateMinimum)
|
||||
// Deposit enough cover to ensure the fee goes to broker owner
|
||||
// The default coverRateMinimum is 10%, so for a 10,000 loan we need
|
||||
// at least 1,000 cover. Default cover is 1,000, so we add more to be
|
||||
// safe.
|
||||
auto const additionalCover = MPT(50'000).value();
|
||||
env(loanBroker::coverDeposit(
|
||||
broker, brokerInfo.brokerID, STAmount{MPT, additionalCover}));
|
||||
env.close();
|
||||
// Verify broker owner is authorized
|
||||
auto const brokerMpt = keylet::mptoken(mptt.issuanceID(), broker);
|
||||
BEAST_EXPECT(env.le(brokerMpt) != nullptr);
|
||||
// Remove the credentials for the Broker owner.
|
||||
// First, pay any positive balance to issuer to zero it out
|
||||
auto const brokerBalance = env.balance(broker, MPT);
|
||||
env(pay(broker, issuer, brokerBalance));
|
||||
env.close();
|
||||
|
||||
env(credentials::deleteCred(broker, broker, issuer, credType));
|
||||
env.close();
|
||||
|
||||
// Make sure the broker is not authorized to hold the MPT after we
|
||||
// deleted the credentials
|
||||
env(pay(issuer, broker, MPT(1'000)), ter(tecNO_AUTH));
|
||||
|
||||
// Now borrower tries to make a payment
|
||||
// We should get a tesSUCCESS instead of a tecNO_AUTH.
|
||||
auto const borrowerBalance = env.balance(borrower, MPT);
|
||||
env(pay(borrower, keylet.key, MPT(10'100)),
|
||||
fee(XRP(100)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
// Verify broker is still not authorized
|
||||
env(pay(issuer, broker, MPT(1'000)), ter(tecNO_AUTH));
|
||||
// Verify the service fee went to the broker pseudo-account
|
||||
if (auto const brokerSle =
|
||||
env.le(keylet::loanbroker(brokerInfo.brokerID));
|
||||
BEAST_EXPECT(brokerSle))
|
||||
{
|
||||
Account const pseudo("pseudo-account", brokerSle->at(sfAccount));
|
||||
auto const balance = env.balance(pseudo, MPT);
|
||||
// 1,000 default + 50,000 extra + 100 service fee from LoanPay
|
||||
BEAST_EXPECTS(
|
||||
balance == MPT(51'100), to_string(Json::Value(balance)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testLoanSetBrokerOwnerNoPermissionedDomainMPT()
|
||||
{
|
||||
testcase
|
||||
<< "LoanSet Broker Owner without permissioned domain of the MPT";
|
||||
using namespace jtx;
|
||||
using namespace loan;
|
||||
|
||||
Account const issuer("issuer");
|
||||
Account const borrower("borrower");
|
||||
Account const broker("broker");
|
||||
|
||||
Env env(*this, all);
|
||||
env.fund(XRP(20'000), issuer, broker, borrower);
|
||||
env.close();
|
||||
|
||||
auto credType = "credential1";
|
||||
|
||||
pdomain::Credentials const credentials1{{issuer, credType}};
|
||||
env(pdomain::setTx(issuer, credentials1));
|
||||
env.close();
|
||||
|
||||
auto domainID = pdomain::getNewDomain(env.meta());
|
||||
|
||||
// Add credentials for the broker and borrower
|
||||
env(credentials::create(broker, issuer, credType));
|
||||
env(credentials::accept(broker, issuer, credType));
|
||||
env.close();
|
||||
|
||||
env(credentials::create(borrower, issuer, credType));
|
||||
env(credentials::accept(borrower, issuer, credType));
|
||||
env.close();
|
||||
|
||||
MPTTester mptt{env, issuer, mptInitNoFund};
|
||||
mptt.create({
|
||||
.flags = tfMPTCanClawback | tfMPTRequireAuth | tfMPTCanTransfer |
|
||||
tfMPTCanLock,
|
||||
.domainID = domainID,
|
||||
});
|
||||
|
||||
PrettyAsset const MPT{mptt.issuanceID()};
|
||||
|
||||
// Authorize broker and borrower
|
||||
mptt.authorize({.account = broker});
|
||||
mptt.authorize({.account = borrower});
|
||||
env.close();
|
||||
|
||||
// Fund accounts
|
||||
env(pay(issuer, broker, MPT(10'000'000)));
|
||||
env(pay(issuer, borrower, MPT(1'000)));
|
||||
env.close();
|
||||
|
||||
// Create vault and broker
|
||||
auto const brokerInfo = createVaultAndBroker(env, MPT, broker);
|
||||
|
||||
// Remove the credentials for the Broker owner.
|
||||
// Clear the balance first.
|
||||
auto const brokerBalance = env.balance(broker, MPT);
|
||||
env(pay(broker, issuer, brokerBalance));
|
||||
env.close();
|
||||
// Delete the credentials
|
||||
env(credentials::deleteCred(broker, broker, issuer, credType));
|
||||
env.close();
|
||||
|
||||
// Create a loan, this should fail for tecNO_AUTH
|
||||
env(set(borrower, brokerInfo.brokerID, 10'000),
|
||||
sig(sfCounterpartySignature, broker),
|
||||
loanServiceFee(MPT(100).value()),
|
||||
paymentInterval(100),
|
||||
fee(XRP(100)),
|
||||
ter(tecNO_AUTH));
|
||||
env.close();
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -7086,6 +7431,10 @@ public:
|
||||
testBorrowerIsBroker();
|
||||
testIssuerIsBorrower();
|
||||
testLimitExceeded();
|
||||
testLoanPayBrokerOwnerMissingTrustline();
|
||||
testLoanPayBrokerOwnerUnauthorizedMPT();
|
||||
testLoanPayBrokerOwnerNoPermissionedDomainMPT();
|
||||
testLoanSetBrokerOwnerNoPermissionedDomainMPT();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -287,6 +287,14 @@ LoanBrokerCoverClawback::preclaim(PreclaimContext const& ctx)
|
||||
// Check if the vault asset issuer has the correct flags
|
||||
auto const sleIssuer =
|
||||
ctx.view.read(keylet::account(vaultAsset.getIssuer()));
|
||||
if (!sleIssuer)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx.j.fatal()) << "Issuer account does not exist.";
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return preclaimHelper<T>(ctx, *sleIssuer, clawAmount);
|
||||
|
||||
@@ -89,6 +89,18 @@ LoanBrokerSet::preclaim(PreclaimContext const& ctx)
|
||||
JLOG(ctx.j.warn()) << "Account is not the owner of the LoanBroker.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
if (auto const debtMax = tx[~sfDebtMaximum])
|
||||
{
|
||||
// Can't reduce the debt maximum below the current total debt
|
||||
auto const currentDebtTotal = sleBroker->at(sfDebtTotal);
|
||||
if (*debtMax != 0 && *debtMax < currentDebtTotal)
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "Cannot reduce DebtMaximum below current DebtTotal.";
|
||||
return tecLIMIT_EXCEEDED;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -223,11 +223,13 @@ LoanManage::defaultLoan(
|
||||
}
|
||||
if (*vaultAvailableProxy > *vaultTotalProxy)
|
||||
{
|
||||
JLOG(j.warn()) << "Vault assets available must not be greater "
|
||||
"than assets outstanding. Available: "
|
||||
<< *vaultAvailableProxy
|
||||
<< ", Total: " << *vaultTotalProxy;
|
||||
return tecLIMIT_EXCEEDED;
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal())
|
||||
<< "Vault assets available must not be greater "
|
||||
"than assets outstanding. Available: "
|
||||
<< *vaultAvailableProxy << ", Total: " << *vaultTotalProxy;
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// The loss has been realized
|
||||
@@ -338,7 +340,7 @@ LoanManage::impairLoan(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
[[nodiscard]] TER
|
||||
LoanManage::unimpairLoan(
|
||||
ApplyView& view,
|
||||
SLE::ref loanSle,
|
||||
|
||||
@@ -49,7 +49,7 @@ public:
|
||||
|
||||
/** Helper function that might be needed by other transactors
|
||||
*/
|
||||
static TER
|
||||
[[nodiscard]] static TER
|
||||
unimpairLoan(
|
||||
ApplyView& view,
|
||||
SLE::ref loanSle,
|
||||
|
||||
@@ -262,9 +262,10 @@ LoanPay::doApply()
|
||||
auto debtTotalProxy = brokerSle->at(sfDebtTotal);
|
||||
|
||||
// Send the broker fee to the owner if they have sufficient cover available,
|
||||
// _and_ if the owner can receive funds. If not, so as not to block the
|
||||
// payment, add it to the cover balance (send it to the broker pseudo
|
||||
// account).
|
||||
// _and_ if the owner can receive funds
|
||||
// _and_ if the broker is authorized to hold funds. If not, so as not to
|
||||
// block the payment, add it to the cover balance (send it to the broker
|
||||
// pseudo account).
|
||||
//
|
||||
// Normally freeze status is checked in preflight, but we do it here to
|
||||
// avoid duplicating the check. It'll claim a fee either way.
|
||||
@@ -278,7 +279,8 @@ LoanPay::doApply()
|
||||
asset,
|
||||
tenthBipsOfValue(debtTotalProxy.value(), coverRateMinimum),
|
||||
loanScale) &&
|
||||
!isDeepFrozen(view, brokerOwner, asset);
|
||||
!isDeepFrozen(view, brokerOwner, asset) &&
|
||||
!requireAuth(view, asset, brokerOwner, AuthType::StrongAuth);
|
||||
}();
|
||||
|
||||
auto const brokerPayee =
|
||||
@@ -305,7 +307,12 @@ LoanPay::doApply()
|
||||
// change will be discarded.
|
||||
if (loanSle->isFlag(lsfLoanImpaired))
|
||||
{
|
||||
LoanManage::unimpairLoan(view, loanSle, vaultSle, asset, j_);
|
||||
if (auto const ret =
|
||||
LoanManage::unimpairLoan(view, loanSle, vaultSle, asset, j_))
|
||||
{
|
||||
JLOG(j_.fatal()) << "Failed to unimpair loan before payment.";
|
||||
return ret; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
LoanPaymentType const paymentType = [&tx]() {
|
||||
@@ -447,6 +454,10 @@ LoanPay::doApply()
|
||||
if (*assetsAvailableProxy > *assetsTotalProxy)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal())
|
||||
<< "Vault assets available must not be greater "
|
||||
"than assets outstanding. Available: "
|
||||
<< *assetsAvailableProxy << ", Total: " << *assetsTotalProxy;
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
@@ -538,12 +538,12 @@ LoanSet::doApply()
|
||||
// ignore tecDUPLICATE. That means the holding already exists,
|
||||
// and is fine here
|
||||
return ter;
|
||||
|
||||
if (auto const ter = requireAuth(
|
||||
view, vaultAsset, brokerOwner, AuthType::StrongAuth))
|
||||
return ter;
|
||||
}
|
||||
|
||||
if (auto const ter =
|
||||
requireAuth(view, vaultAsset, brokerOwner, AuthType::StrongAuth))
|
||||
return ter;
|
||||
|
||||
if (auto const ter = accountSendMulti(
|
||||
view,
|
||||
vaultPseudo,
|
||||
|
||||
Reference in New Issue
Block a user