@@ -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+
70297164public:
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
0 commit comments