Skip to content

Commit 6bec1f0

Browse files
committed
Fix the bug
Signed-off-by: JCW <[email protected]>
1 parent 020ea3f commit 6bec1f0

File tree

2 files changed

+144
-4
lines changed

2 files changed

+144
-4
lines changed

src/test/app/Loan_test.cpp

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7026,6 +7026,141 @@ class Loan_test : public beast::unit_test::suite
70267026
paymentParams);
70277027
}
70287028

7029+
void
7030+
testLoanPayBrokerOwnerMissingTrustline()
7031+
{
7032+
testcase << "LoanPay Broker Owner Missing Trustline (PoC)";
7033+
using namespace jtx;
7034+
using namespace loan;
7035+
Account const issuer("issuer");
7036+
Account const borrower("borrower");
7037+
Account const broker("broker");
7038+
auto const IOU = issuer["IOU"];
7039+
Env env(*this, all);
7040+
env.fund(XRP(20'000), issuer, broker, borrower);
7041+
env.close();
7042+
// Set up trustlines and fund accounts
7043+
env(trust(broker, IOU(20'000'000)));
7044+
env(trust(borrower, IOU(20'000'000)));
7045+
env(pay(issuer, broker, IOU(10'000'000)));
7046+
env(pay(issuer, borrower, IOU(1'000)));
7047+
env.close();
7048+
// Create vault and broker
7049+
auto const brokerInfo = createVaultAndBroker(env, IOU, broker);
7050+
// Create a loan first (this creates debt)
7051+
auto const keylet = keylet::loan(brokerInfo.brokerID, 1);
7052+
env(set(borrower, brokerInfo.brokerID, 10'000),
7053+
sig(sfCounterpartySignature, broker),
7054+
loanServiceFee(IOU(100).value()),
7055+
paymentInterval(100),
7056+
fee(XRP(100)));
7057+
env.close();
7058+
// Ensure broker has sufficient cover so brokerPayee == brokerOwner
7059+
// We need coverAvailable >= (debtTotal * coverRateMinimum)
7060+
// Deposit enough cover to ensure the fee goes to broker owner
7061+
// The default coverRateMinimum is 10%, so for a 10,000 loan we need
7062+
// at least 1,000 cover. Default cover is 1,000, so we add more to be
7063+
// safe.
7064+
auto const additionalCover = IOU(50'000).value();
7065+
env(loanBroker::coverDeposit(
7066+
broker, brokerInfo.brokerID, STAmount{IOU, additionalCover}));
7067+
env.close();
7068+
// Verify broker owner has a trustline
7069+
auto const brokerTrustline = keylet::line(broker, IOU);
7070+
BEAST_EXPECT(env.le(brokerTrustline) != nullptr);
7071+
// Broker owner deletes their trustline
7072+
// First, pay any positive balance to issuer to zero it out
7073+
auto const brokerBalance = env.balance(broker, IOU);
7074+
env(pay(broker, issuer, brokerBalance));
7075+
env.close();
7076+
// Remove the trustline by setting limit to 0
7077+
env(trust(broker, IOU(0)));
7078+
env.close();
7079+
// Verify trustline is deleted
7080+
BEAST_EXPECT(env.le(brokerTrustline) == nullptr);
7081+
// Now borrower tries to make a payment
7082+
// This should fail with tecNO_LINE
7083+
env(pay(borrower, keylet.key, IOU(10'100)),
7084+
fee(XRP(100)),
7085+
ter(tesSUCCESS));
7086+
env.close();
7087+
}
7088+
7089+
void
7090+
testLoanPayBrokerOwnerUnauthorizedMPT()
7091+
{
7092+
testcase << "LoanPay Broker Owner ";
7093+
using namespace jtx;
7094+
using namespace loan;
7095+
7096+
Account const issuer("issuer");
7097+
Account const borrower("borrower");
7098+
Account const broker("broker");
7099+
7100+
Env env(*this, all);
7101+
env.fund(XRP(20'000), issuer, broker, borrower);
7102+
env.close();
7103+
7104+
MPTTester mptt{env, issuer, mptInitNoFund};
7105+
mptt.create(
7106+
{.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
7107+
7108+
PrettyAsset const MPT{mptt.issuanceID()};
7109+
7110+
// Authorize broker and borrower
7111+
mptt.authorize({.account = broker});
7112+
mptt.authorize({.account = borrower});
7113+
7114+
env.close();
7115+
7116+
// Fund accounts
7117+
env(pay(issuer, broker, MPT(10'000'000)));
7118+
env(pay(issuer, borrower, MPT(1'000)));
7119+
env.close();
7120+
7121+
// Create vault and broker
7122+
auto const brokerInfo = createVaultAndBroker(env, MPT, broker);
7123+
// Create a loan first (this creates debt)
7124+
auto const keylet = keylet::loan(brokerInfo.brokerID, 1);
7125+
env(set(borrower, brokerInfo.brokerID, 10'000),
7126+
sig(sfCounterpartySignature, broker),
7127+
loanServiceFee(MPT(100).value()),
7128+
paymentInterval(100),
7129+
fee(XRP(100)));
7130+
env.close();
7131+
// Ensure broker has sufficient cover so brokerPayee == brokerOwner
7132+
// We need coverAvailable >= (debtTotal * coverRateMinimum)
7133+
// Deposit enough cover to ensure the fee goes to broker owner
7134+
// The default coverRateMinimum is 10%, so for a 10,000 loan we need
7135+
// at least 1,000 cover. Default cover is 1,000, so we add more to be
7136+
// safe.
7137+
auto const additionalCover = MPT(50'000).value();
7138+
env(loanBroker::coverDeposit(
7139+
broker, brokerInfo.brokerID, STAmount{MPT, additionalCover}));
7140+
env.close();
7141+
// Verify broker owner is authorized
7142+
auto const brokerMpt = keylet::mptoken(mptt.issuanceID(), broker);
7143+
BEAST_EXPECT(env.le(brokerMpt) != nullptr);
7144+
// Broker owner unauthorizes.
7145+
// First, pay any positive balance to issuer to zero it out
7146+
auto const brokerBalance = env.balance(broker, MPT);
7147+
env(pay(broker, issuer, brokerBalance));
7148+
env.close();
7149+
// Then, unauthorize the MPT.
7150+
mptt.authorize({.account = broker, .flags = tfMPTUnauthorize});
7151+
env.close();
7152+
// Verify the MPT is unauthorized.
7153+
BEAST_EXPECT(env.le(brokerMpt) == nullptr);
7154+
// Now borrower tries to make a payment
7155+
// This should fail with tecNO_LINE
7156+
7157+
auto const borrowerBalance = env.balance(borrower, MPT);
7158+
env(pay(borrower, keylet.key, MPT(10'100)),
7159+
fee(XRP(100)),
7160+
ter(tesSUCCESS));
7161+
env.close();
7162+
}
7163+
70297164
public:
70307165
void
70317166
run() override
@@ -7074,6 +7209,8 @@ class Loan_test : public beast::unit_test::suite
70747209
testBorrowerIsBroker();
70757210
testIssuerIsBorrower();
70767211
testLimitExceeded();
7212+
testLoanPayBrokerOwnerMissingTrustline();
7213+
testLoanPayBrokerOwnerUnauthorizedMPT();
70777214
}
70787215
};
70797216

src/xrpld/app/tx/detail/LoanPay.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,10 @@ LoanPay::doApply()
262262
auto debtTotalProxy = brokerSle->at(sfDebtTotal);
263263

264264
// Send the broker fee to the owner if they have sufficient cover available,
265-
// _and_ if the owner can receive funds. If not, so as not to block the
266-
// payment, add it to the cover balance (send it to the broker pseudo
267-
// account).
265+
// _and_ if the owner can receive funds
266+
// _and_ if the broker is authorized to hold funds. If not, so as not to
267+
// block the payment, add it to the cover balance (send it to the broker
268+
// pseudo account).
268269
//
269270
// Normally freeze status is checked in preflight, but we do it here to
270271
// avoid duplicating the check. It'll claim a fee either way.
@@ -278,7 +279,9 @@ LoanPay::doApply()
278279
asset,
279280
tenthBipsOfValue(debtTotalProxy.value(), coverRateMinimum),
280281
loanScale) &&
281-
!isDeepFrozen(view, brokerOwner, asset);
282+
!isDeepFrozen(view, brokerOwner, asset) &&
283+
requireAuth(view, asset, brokerOwner, AuthType::StrongAuth) ==
284+
tesSUCCESS;
282285
}();
283286

284287
auto const brokerPayee =

0 commit comments

Comments
 (0)