From da10ba6fd0528af5524620a55c99dec03d7438a8 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Fri, 11 Apr 2025 20:13:48 -0400 Subject: [PATCH] Start adding tests for LoanBrokerDelete - Start fixing the inevitable problems found along the way - Generalize the LoanBroker lifecycle test --- .../xrpl/protocol/detail/transactions.macro | 4 +- src/test/app/LoanBroker_test.cpp | 457 ++++++++++-------- src/test/jtx/TestHelpers.h | 10 +- src/test/jtx/impl/TestHelpers.cpp | 11 + src/xrpld/app/tx/detail/InvariantCheck.cpp | 1 - src/xrpld/app/tx/detail/LoanBrokerDelete.cpp | 22 +- 6 files changed, 280 insertions(+), 225 deletions(-) diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index 56d0a5e930..aeb2d38371 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -548,13 +548,13 @@ TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet, {sfCoverRateLiquidation, soeOPTIONAL}, })) -#if 0 /** This transaction deletes a Loan Broker */ TRANSACTION(ttLOAN_BROKER_DELETE, 75, LoanBrokerDelete, - acctDelete | mayAuthorizeMPT, ({ + mustDeleteAcct | mayAuthorizeMPT, ({ {sfLoanBrokerID, soeREQUIRED}, })) +#if 0 /** This transaction deposits First Loss Capital into a Loan Broker */ TRANSACTION(ttLOAN_BROKER_COVER_DEPOSIT, 76, LoanBrokerCoverDeposit, noPriv, ({ {sfLoanBrokerID, soeREQUIRED}, diff --git a/src/test/app/LoanBroker_test.cpp b/src/test/app/LoanBroker_test.cpp index e8b450e886..db20b37d88 100644 --- a/src/test/app/LoanBroker_test.cpp +++ b/src/test/app/LoanBroker_test.cpp @@ -73,6 +73,10 @@ class LoanBroker_test : public beast::unit_test::suite using namespace loanBroker; // Can't create a loan broker regardless of whether the vault exists env(set(alice, keylet.key), fee(increment), ter(temDISABLED)); + auto const brokerKeylet = + keylet::loanbroker(alice.id(), env.seq(alice)); + // LoanBrokerDelete is disabled, too. + env(del(alice, brokerKeylet.key), ter(temDISABLED)); }; failAll(all - featureMPTokensV1); failAll(all - featureSingleAssetVault - featureLendingProtocol); @@ -80,8 +84,112 @@ class LoanBroker_test : public beast::unit_test::suite failAll(all - featureLendingProtocol, true); } + struct VaultInfo + { + jtx::PrettyAsset asset; + uint256 vaultID; + VaultInfo(jtx::PrettyAsset const& asset_, uint256 const& vaultID_) + : asset(asset_), vaultID(vaultID_) + { + } + }; + void - testCreateAndUpdate() + lifecycle( + jtx::Env& env, + jtx::Account const& alice, + jtx::Account const& evan, + VaultInfo const& vault, + std::function createBroker, + std::function checkBroker, + std::function changeBroker, + std::function checkChangedBroker) + { + auto const keylet = keylet::loanbroker(alice.id(), env.seq(alice)); + + using namespace jtx; + using namespace loanBroker; + + createBroker(); + env.close(); + if (auto broker = env.le(keylet); BEAST_EXPECT(broker)) + { + BEAST_EXPECT(broker->at(sfVaultID) == vault.vaultID); + BEAST_EXPECT(broker->at(sfAccount) != alice.id()); + BEAST_EXPECT(broker->at(sfOwner) == alice.id()); + BEAST_EXPECT(broker->at(sfFlags) == 0); + BEAST_EXPECT(broker->at(sfSequence) == env.seq(alice) - 1); + BEAST_EXPECT(broker->at(sfOwnerCount) == 0); + BEAST_EXPECT(broker->at(sfDebtTotal) == 0); + BEAST_EXPECT(broker->at(sfDebtMaximum) == 0); + BEAST_EXPECT(broker->at(sfCoverAvailable) == 0); + BEAST_EXPECT(broker->at(sfCoverRateMinimum) == 0); + BEAST_EXPECT(broker->at(sfCoverRateLiquidation) == 0); + checkBroker(broker); + + // Load the pseudo-account + auto const pseudoKeylet = keylet::account(broker->at(sfAccount)); + if (auto const pseudo = env.le(pseudoKeylet); BEAST_EXPECT(pseudo)) + { + BEAST_EXPECT( + pseudo->at(sfFlags) == + (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth)); + BEAST_EXPECT(pseudo->at(sfSequence) == 0); + BEAST_EXPECT(pseudo->at(sfBalance) == beast::zero); + BEAST_EXPECT( + pseudo->at(sfOwnerCount) == + (vault.asset.raw().native() ? 0 : 1)); + BEAST_EXPECT(!pseudo->isFieldPresent(sfAccountTxnID)); + BEAST_EXPECT(!pseudo->isFieldPresent(sfRegularKey)); + BEAST_EXPECT(!pseudo->isFieldPresent(sfEmailHash)); + BEAST_EXPECT(!pseudo->isFieldPresent(sfWalletLocator)); + BEAST_EXPECT(!pseudo->isFieldPresent(sfWalletSize)); + BEAST_EXPECT(!pseudo->isFieldPresent(sfMessageKey)); + BEAST_EXPECT(!pseudo->isFieldPresent(sfTransferRate)); + BEAST_EXPECT(!pseudo->isFieldPresent(sfDomain)); + BEAST_EXPECT(!pseudo->isFieldPresent(sfTickSize)); + BEAST_EXPECT(!pseudo->isFieldPresent(sfTicketCount)); + BEAST_EXPECT(!pseudo->isFieldPresent(sfNFTokenMinter)); + BEAST_EXPECT(!pseudo->isFieldPresent(sfMintedNFTokens)); + BEAST_EXPECT(!pseudo->isFieldPresent(sfBurnedNFTokens)); + BEAST_EXPECT(!pseudo->isFieldPresent(sfFirstNFTokenSequence)); + BEAST_EXPECT(!pseudo->isFieldPresent(sfAMMID)); + BEAST_EXPECT(!pseudo->isFieldPresent(sfVaultID)); + BEAST_EXPECT(pseudo->at(sfLoanBrokerID) == keylet.key); + } + + // no-op + env(set(alice, vault.vaultID), loanBrokerID(keylet.key)); + + // Make modifications to the broker + changeBroker(broker); + + env.close(); + broker = env.le(keylet); + + // Check the results of modifications + checkChangedBroker(broker); + + ///////////////////////////////////// + // 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 + // delete the broker + env(del(alice, keylet.key)); + env.close(); + { + broker = env.le(keylet); + BEAST_EXPECT(!broker); + auto pseudo = env.le(pseudoKeylet); + BEAST_EXPECT(!pseudo); + } + } + } + + void + testLifecycle() { testcase("Create and update"); using namespace jtx; @@ -121,15 +229,6 @@ class LoanBroker_test : public beast::unit_test::suite std::array const assets{xrpAsset, iouAsset, mptAsset}; // Create vaults - struct VaultInfo - { - PrettyAsset asset; - uint256 vaultID; - VaultInfo(PrettyAsset const& asset_, uint256 const& vaultID_) - : asset(asset_), vaultID(vaultID_) - { - } - }; std::vector vaults; for (auto const& asset : assets) { @@ -150,146 +249,102 @@ class LoanBroker_test : public beast::unit_test::suite { using namespace loanBroker; - { - auto badKeylet = keylet::vault(alice.id(), env.seq(alice)); - // Try some failure cases - // insufficient fee - env(set(evan, vault.vaultID), ter(telINSUF_FEE_P)); - // not the vault owner - env(set(evan, vault.vaultID), - fee(increment), - ter(tecNO_PERMISSION)); - // not a vault - env(set(alice, badKeylet.key), - fee(increment), - ter(tecNO_ENTRY)); - // flags are checked first - env(set(evan, vault.vaultID, ~tfUniversal), - fee(increment), - ter(temINVALID_FLAG)); - // field length validation - // sfData: good length, bad account - env(set(evan, vault.vaultID), - fee(increment), - data(std::string(maxDataPayloadLength, 'X')), - ter(tecNO_PERMISSION)); - // sfData: too long - env(set(evan, vault.vaultID), - fee(increment), - data(std::string(maxDataPayloadLength + 1, 'Y')), - ter(temINVALID)); - // sfManagementFeeRate: good value, bad account - env(set(evan, vault.vaultID), - managementFeeRate(maxFeeRate), - fee(increment), - ter(tecNO_PERMISSION)); - // sfManagementFeeRate: too big - env(set(evan, vault.vaultID), - managementFeeRate(maxFeeRate + 1), - fee(increment), - ter(temINVALID)); - // sfCoverRateMinimum: good value, bad account - env(set(evan, vault.vaultID), - coverRateMinimum(maxCoverRate), - fee(increment), - ter(tecNO_PERMISSION)); - // sfCoverRateMinimum: too big - env(set(evan, vault.vaultID), - coverRateMinimum(maxCoverRate + 1), - fee(increment), - ter(temINVALID)); - // sfCoverRateLiquidation: good value, bad account - env(set(evan, vault.vaultID), - coverRateLiquidation(maxCoverRate), - fee(increment), - ter(tecNO_PERMISSION)); - // sfCoverRateLiquidation: too big - env(set(evan, vault.vaultID), - coverRateLiquidation(maxCoverRate + 1), - fee(increment), - ter(temINVALID)); - // sfDebtMaximum: good value, bad account - env(set(evan, vault.vaultID), - debtMaximum(Number(0)), - fee(increment), - ter(tecNO_PERMISSION)); - // sfDebtMaximum: overflow - env(set(evan, vault.vaultID), - debtMaximum(Number(1, 100)), - fee(increment), - ter(temINVALID)); - // sfDebtMaximum: negative - env(set(evan, vault.vaultID), - debtMaximum(Number(-1)), - fee(increment), - ter(temINVALID)); + auto badKeylet = keylet::vault(alice.id(), env.seq(alice)); + // Try some failure cases + // insufficient fee + env(set(evan, vault.vaultID), ter(telINSUF_FEE_P)); + // not the vault owner + env(set(evan, vault.vaultID), + fee(increment), + ter(tecNO_PERMISSION)); + // not a vault + env(set(alice, badKeylet.key), fee(increment), ter(tecNO_ENTRY)); + // flags are checked first + env(set(evan, vault.vaultID, ~tfUniversal), + fee(increment), + ter(temINVALID_FLAG)); + // field length validation + // sfData: good length, bad account + env(set(evan, vault.vaultID), + fee(increment), + data(std::string(maxDataPayloadLength, 'X')), + ter(tecNO_PERMISSION)); + // sfData: too long + env(set(evan, vault.vaultID), + fee(increment), + data(std::string(maxDataPayloadLength + 1, 'Y')), + ter(temINVALID)); + // sfManagementFeeRate: good value, bad account + env(set(evan, vault.vaultID), + managementFeeRate(maxFeeRate), + fee(increment), + ter(tecNO_PERMISSION)); + // sfManagementFeeRate: too big + env(set(evan, vault.vaultID), + managementFeeRate(maxFeeRate + 1), + fee(increment), + ter(temINVALID)); + // sfCoverRateMinimum: good value, bad account + env(set(evan, vault.vaultID), + coverRateMinimum(maxCoverRate), + fee(increment), + ter(tecNO_PERMISSION)); + // sfCoverRateMinimum: too big + env(set(evan, vault.vaultID), + coverRateMinimum(maxCoverRate + 1), + fee(increment), + ter(temINVALID)); + // sfCoverRateLiquidation: good value, bad account + env(set(evan, vault.vaultID), + coverRateLiquidation(maxCoverRate), + fee(increment), + ter(tecNO_PERMISSION)); + // sfCoverRateLiquidation: too big + env(set(evan, vault.vaultID), + coverRateLiquidation(maxCoverRate + 1), + fee(increment), + ter(temINVALID)); + // sfDebtMaximum: good value, bad account + env(set(evan, vault.vaultID), + debtMaximum(Number(0)), + fee(increment), + ter(tecNO_PERMISSION)); + // sfDebtMaximum: overflow + env(set(evan, vault.vaultID), + debtMaximum(Number(1, 100)), + fee(increment), + ter(temINVALID)); + // sfDebtMaximum: negative + env(set(evan, vault.vaultID), + debtMaximum(Number(-1)), + fee(increment), + ter(temINVALID)); - auto keylet = keylet::loanbroker(alice.id(), env.seq(alice)); - // Successfully create a Loan Broker with all default values. - env(set(alice, vault.vaultID), fee(increment)); - env.close(); - if (auto broker = env.le(keylet); BEAST_EXPECT(broker)) - { - // Check the fields - BEAST_EXPECT(broker->at(sfVaultID) == vault.vaultID); - BEAST_EXPECT(broker->at(sfAccount) != alice.id()); - BEAST_EXPECT(broker->at(sfOwner) == alice.id()); + lifecycle( + env, + alice, + evan, + vault, + [&]() { + // Successfully create a Loan Broker with all default + // values. + env(set(alice, vault.vaultID), fee(increment)); + }, + [&](SLE::const_ref broker) { + // Extra checks BEAST_EXPECT(!broker->isFieldPresent(sfManagementFeeRate)); BEAST_EXPECT(!broker->isFieldPresent(sfCoverRateMinimum)); BEAST_EXPECT( !broker->isFieldPresent(sfCoverRateLiquidation)); - BEAST_EXPECT(broker->at(sfFlags) == 0); - BEAST_EXPECT(broker->at(sfSequence) == env.seq(alice) - 1); BEAST_EXPECT(!broker->isFieldPresent(sfData)); - BEAST_EXPECT(broker->at(sfOwnerCount) == 0); - BEAST_EXPECT(broker->at(sfDebtTotal) == 0); - BEAST_EXPECT(broker->at(sfDebtMaximum) == 0); - BEAST_EXPECT(broker->at(sfCoverAvailable) == 0); - BEAST_EXPECT(broker->at(sfCoverRateMinimum) == 0); - BEAST_EXPECT(broker->at(sfCoverRateLiquidation) == 0); - - // Load the pseudo-account - auto const pseudoKeylet = - keylet::account(broker->at(sfAccount)); - if (auto pseudo = env.le(pseudoKeylet); - BEAST_EXPECT(pseudo)) - { - BEAST_EXPECT( - pseudo->at(sfFlags) == - (lsfDisableMaster | lsfDefaultRipple | - lsfDepositAuth)); - BEAST_EXPECT(pseudo->at(sfSequence) == 0); - BEAST_EXPECT(pseudo->at(sfBalance) == beast::zero); - BEAST_EXPECT( - pseudo->at(sfOwnerCount) == - (vault.asset.raw().native() ? 0 : 1)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfAccountTxnID)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfRegularKey)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfEmailHash)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfWalletLocator)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfWalletSize)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfMessageKey)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfTransferRate)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfDomain)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfTickSize)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfTicketCount)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfNFTokenMinter)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfMintedNFTokens)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfBurnedNFTokens)); - BEAST_EXPECT( - !pseudo->isFieldPresent(sfFirstNFTokenSequence)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfAMMID)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfVaultID)); - BEAST_EXPECT(pseudo->at(sfLoanBrokerID) == keylet.key); - } + }, + [&](SLE::const_ref broker) { + // Modifications // Update the fields - auto nextKeylet = + auto const nextKeylet = keylet::loanbroker(alice.id(), env.seq(alice)); - // no-op - env(set(alice, vault.vaultID), loanBrokerID(keylet.key)); - // fields that can't be changed // LoanBrokerID env(set(alice, vault.vaultID), @@ -297,25 +352,25 @@ class LoanBroker_test : public beast::unit_test::suite ter(tecNO_ENTRY)); // VaultID env(set(alice, nextKeylet.key), - loanBrokerID(keylet.key), + loanBrokerID(broker->key()), ter(tecNO_PERMISSION)); // Owner env(set(evan, vault.vaultID), - loanBrokerID(keylet.key), + loanBrokerID(broker->key()), ter(tecNO_PERMISSION)); // ManagementFeeRate env(set(alice, vault.vaultID), - loanBrokerID(keylet.key), + loanBrokerID(broker->key()), managementFeeRate(maxFeeRate), ter(temINVALID)); // CoverRateMinimum env(set(alice, vault.vaultID), - loanBrokerID(keylet.key), + loanBrokerID(broker->key()), coverRateMinimum(maxFeeRate), ter(temINVALID)); // CoverRateLiquidation env(set(alice, vault.vaultID), - loanBrokerID(keylet.key), + loanBrokerID(broker->key()), coverRateLiquidation(maxFeeRate), ter(temINVALID)); @@ -323,96 +378,70 @@ class LoanBroker_test : public beast::unit_test::suite std::string const testData("Test Data 1234"); // Bad data: too long env(set(alice, vault.vaultID), - loanBrokerID(keylet.key), + loanBrokerID(broker->key()), data(std::string(maxDataPayloadLength + 1, 'W')), ter(temINVALID)); // Debt maximum: explicit 0 env(set(alice, vault.vaultID), - loanBrokerID(keylet.key), + loanBrokerID(broker->key()), debtMaximum(Number(0))); + // Check the updated fields + broker = env.le(broker->key()); + BEAST_EXPECT(!broker->isFieldPresent(sfDebtMaximum)); // Bad debt maximum env(set(alice, vault.vaultID), - loanBrokerID(keylet.key), + loanBrokerID(broker->key()), debtMaximum(Number(-175, -1)), ter(temINVALID)); // Data & Debt maximum env(set(alice, vault.vaultID), - loanBrokerID(keylet.key), + loanBrokerID(broker->key()), data(testData), debtMaximum(Number(175, -1))); - env.close(); + }, + [&](SLE::const_ref broker) { // Check the updated fields - broker = env.le(keylet); BEAST_EXPECT(checkVL(broker->at(sfData), testData)); BEAST_EXPECT(broker->at(sfDebtMaximum) == Number(175, -1)); - } + }); - auto keylet2 = keylet::loanbroker(alice.id(), env.seq(alice)); - std::string const testData2("spam spam spam spam"); - // Finally, create another Loan Broker with none of the values - // at default - // Successfully create a Loan Broker with no default values. - env(set(alice, vault.vaultID), - data(testData2), - managementFeeRate(123), - debtMaximum(Number(9)), - coverRateMinimum(100), - coverRateLiquidation(200), - fee(increment)); - env.close(); - if (auto broker = env.le(keylet2); BEAST_EXPECT(broker)) - { - // Check the fields - BEAST_EXPECT(broker->at(sfVaultID) == vault.vaultID); - BEAST_EXPECT(broker->at(sfAccount) != alice.id()); - BEAST_EXPECT(broker->at(sfOwner) == alice.id()); + lifecycle( + env, + alice, + evan, + vault, + [&]() { + std::string const testData2("spam spam spam spam"); + // Finally, create another Loan Broker with none of the + // values at default + env(set(alice, vault.vaultID), + data(testData2), + managementFeeRate(123), + debtMaximum(Number(9)), + coverRateMinimum(100), + coverRateLiquidation(200), + fee(increment)); + }, + [&](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(broker->at(sfFlags) == 0); - BEAST_EXPECT(broker->at(sfSequence) == env.seq(alice) - 1); BEAST_EXPECT(checkVL(broker->at(sfData), testData2)); - - BEAST_EXPECT(broker->at(sfOwnerCount) == 0); - BEAST_EXPECT(broker->at(sfDebtTotal) == 0); - BEAST_EXPECT(broker->at(sfCoverAvailable) == 0); - // Load the pseudo-account - auto const pseudoKeylet = - keylet::account(broker->at(sfAccount)); - if (auto pseudo = env.le(pseudoKeylet); - BEAST_EXPECT(pseudo)) - { - BEAST_EXPECT( - pseudo->at(sfFlags) == - (lsfDisableMaster | lsfDefaultRipple | - lsfDepositAuth)); - BEAST_EXPECT(pseudo->at(sfSequence) == 0); - BEAST_EXPECT(pseudo->at(sfBalance) == beast::zero); - BEAST_EXPECT( - pseudo->at(sfOwnerCount) == - (vault.asset.raw().native() ? 0 : 1)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfAccountTxnID)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfRegularKey)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfEmailHash)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfWalletLocator)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfWalletSize)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfMessageKey)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfTransferRate)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfDomain)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfTickSize)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfTicketCount)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfNFTokenMinter)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfMintedNFTokens)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfBurnedNFTokens)); - BEAST_EXPECT( - !pseudo->isFieldPresent(sfFirstNFTokenSequence)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfAMMID)); - BEAST_EXPECT(!pseudo->isFieldPresent(sfVaultID)); - BEAST_EXPECT(pseudo->at(sfLoanBrokerID) == keylet2.key); - } - } - } + }, + [&](SLE::const_ref broker) { + // Reset Data & Debt maximum to default values + env(set(alice, vault.vaultID), + loanBrokerID(keylet.key), + data(""), + debtMaximum(Number(0))); + }, + [&](SLE::const_ref broker) { + // Check the updated fields + BEAST_EXPECT(!broker->isFieldPresent(sfData)); + BEAST_EXPECT(!broker->isFieldPresent(sfDebtMaximum)); + }); } } @@ -421,7 +450,7 @@ public: run() override { testDisabled(); - testCreateAndUpdate(); + testLifecycle(); } }; diff --git a/src/test/jtx/TestHelpers.h b/src/test/jtx/TestHelpers.h index edda769b27..8f3ec1b636 100644 --- a/src/test/jtx/TestHelpers.h +++ b/src/test/jtx/TestHelpers.h @@ -605,13 +605,9 @@ namespace loanBroker { Json::Value set(AccountID const& account, uint256 const& vaultId, uint32_t flags = 0); -/* -inline Json::Value -set(Account const& account, uint256 const& vaultId, uint32_t flags = 0) -{ - return create(account.id(), vaultId); -} -*/ +// Use "del" because "delete" is a reserved word in C++. +Json::Value +del(AccountID const& account, uint256 const& loanBrokerID, uint32_t flags = 0); auto const loanBrokerID = JTxFieldWrapper(sfLoanBrokerID); diff --git a/src/test/jtx/impl/TestHelpers.cpp b/src/test/jtx/impl/TestHelpers.cpp index 5d113df4fa..c5e7ca7c40 100644 --- a/src/test/jtx/impl/TestHelpers.cpp +++ b/src/test/jtx/impl/TestHelpers.cpp @@ -405,6 +405,17 @@ set(AccountID const& account, uint256 const& vaultId, uint32_t flags) return jv; } +Json::Value +del(AccountID const& account, uint256 const& loanBrokerID, uint32_t flags) +{ + Json::Value jv; + jv[sfTransactionType.jsonName] = jss::LoanBrokerDelete; + jv[sfAccount.jsonName] = to_string(account); + jv[sfLoanBrokerID.jsonName] = to_string(loanBrokerID); + jv[sfFlags.jsonName] = flags; + return jv; +} + } // namespace loanBroker } // namespace jtx diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index 4fca45fab5..686d575b27 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -435,7 +435,6 @@ AccountRootsDeletedClean::finalize( // be logged [[maybe_unused]] bool const enforce = view.rules().enabled(featureInvariantsV1_1) || - view.rules().enabled(featureSingleAssetVault) || view.rules().enabled(featureLendingProtocol); auto const objectExists = [&view, enforce, &j](auto const& keylet) { diff --git a/src/xrpld/app/tx/detail/LoanBrokerDelete.cpp b/src/xrpld/app/tx/detail/LoanBrokerDelete.cpp index a9dbecfb7e..ed0871aaf5 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerDelete.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerDelete.cpp @@ -80,7 +80,7 @@ LoanBrokerDelete::preclaim(PreclaimContext const& ctx) } if (sleBroker->at(sfOwnerCount) != 0) { - JLOG(ctx.j.warn()) << "LoanBrokerSet: Owner count is not zero"; + JLOG(ctx.j.warn()) << "LoanBrokerDelete: Owner count is not zero"; return tecHAS_OBLIGATIONS; } @@ -138,6 +138,26 @@ LoanBrokerDelete::doApply() if (!brokerPseudoSLE) return tefBAD_LEDGER; + // Making the payment should have deleted any obligations + // associated with the broker or broker pseudo-account. + if (*brokerPseudoSLE->at(sfBalance)) + { + JLOG(j_.warn()) << "LoanBrokerDelete: Pseudo-account has a balance"; + return tecHAS_OBLIGATIONS; + } + if (brokerPseudoSLE->at(sfOwnerCount) != 0) + { + JLOG(j_.warn()) + << "LoanBrokerDelete: Pseudo-account still owns objects"; + return tecHAS_OBLIGATIONS; + } + if (auto const directory = keylet::ownerDir(brokerPseudoID); + view.read(directory)) + { + JLOG(j_.warn()) << "LoanBrokerDelete: Pseudo-account has a directory"; + return tecHAS_OBLIGATIONS; + } + view.erase(brokerPseudoSLE); view.erase(broker);