Compare commits

...
Author SHA1 Message Date
Kenny Lei 440018c0fe Revert "fix: Reject open-ended vaults at LoanBrokerSet (#8076)"
This reverts commit 421af6db79 for the
lending hackathon branch, so that LoanBrokerSet once again accepts
open-ended vaults when featureLendingProtocolV1_1 is enabled.

The test-side changes from the original PR are reverted as well, so the
affected suites exercise open-ended vault flows again. The only parts
intentionally kept are the jtx helpers Vault::createClosedEnded and
Vault::closePastSubscription (src/test/jtx/vault.h and
src/test/jtx/impl/vault.cpp), because tests merged later in #8075
(VaultBugs_test.cpp, InvariantsMisc_test.cpp) depend on them.

Conflicts resolved:
- src/test/app/invariants/InvariantsBase.cpp: keep the later-added
  <cstdint> include while dropping the PR's <chrono> include.
- src/test/app/lending/LoanTestBase.h: keep the later-added
  `.flags = params.vaultFlags` argument while restoring
  `params.vaultKind`.
2026-09-09 20:18:57 -07:00
15 changed files with 47 additions and 250 deletions

No files matched your search

@@ -8,9 +8,7 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
@@ -146,20 +144,6 @@ LoanBrokerSet::preclaim(PreclaimContext const& ctx)
}
else
{
// LP V1.1: only closed-ended vaults may host a loan broker. The
// lending protocol relies on the closed-ended Subscription /
// Investment / Redemption phase structure; attaching a broker to
// an open-ended vault has no well-defined lifecycle. VaultCreate
// stays unrestricted so existing open-ended flows keep working;
// the constraint is enforced here, at the point where the vault
// is first bound to the lending protocol.
if (ctx.view.rules().enabled(featureLendingProtocolV1_1) &&
getVaultKind(sleVault) != VaultKind::ClosedEnded)
{
JLOG(ctx.j.warn()) << "LoanBroker requires a closed-ended Vault.";
return tecNO_PERMISSION;
}
if (auto const ter = canAddHolding(ctx.view, asset))
return ter;
+1 -9
View File
@@ -3169,12 +3169,7 @@ class Batch_test : public beast::unit_test::Suite
auto const debtMaximumValue = asset(25'000).value();
auto const coverDepositValue = asset(1000).value();
// Under featureLendingProtocolV1_1 LoanBrokerSet::preclaim only
// accepts closed-ended vaults, so build one with a subscription
// window that lets the lender deposit now, then advance the clock
// past SubscriptionDate before creating loans.
auto [tx, vaultKeylet, subscriptionDate] =
vault.createClosedEnded({.owner = lender, .asset = asset});
auto [tx, vaultKeylet] = vault.create({.owner = lender, .asset = asset});
env(tx);
env.close();
BEAST_EXPECT(env.le(vaultKeylet));
@@ -3182,9 +3177,6 @@ class Batch_test : public beast::unit_test::Suite
env(vault.deposit({.depositor = lender, .id = vaultKeylet.key, .amount = deposit}));
env.close();
// Move into the Investment phase before creating loans.
vault.closePastSubscription(subscriptionDate);
auto const brokerKeylet =
keylet::loanBroker(lender.id(), SeqProxy::rawSequence(env.seq(lender)));
+1 -7
View File
@@ -1877,11 +1877,7 @@ public:
PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
Vault const vault{env};
// Under featureLendingProtocolV1_1 LoanBrokerSet::preclaim only
// accepts closed-ended vaults; build one and advance past
// SubscriptionDate before creating a loan.
auto [vaultTx, vaultKeylet, subscriptionDate] =
vault.createClosedEnded({.owner = alice, .asset = xrpAsset});
auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = xrpAsset});
env(vaultTx);
env.close();
@@ -1889,8 +1885,6 @@ public:
{.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(1000)}));
env.close();
vault.closePastSubscription(subscriptionDate);
auto const brokerKeylet =
keylet::loanBroker(alice.id(), SeqProxy::rawSequence(env.seq(alice)));
env(loan_broker::set(alice, vaultKeylet.key),
+2 -11
View File
@@ -20,7 +20,6 @@
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -32,7 +31,6 @@
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/applySteps.h>
#include <chrono>
#include <cstdint>
#include <initializer_list>
#include <memory>
@@ -183,17 +181,10 @@ InvariantsBase::createLoanBroker(
{
using namespace jtx;
// Under featureLendingProtocolV1_1 LoanBrokerSet::preclaim only
// accepts closed-ended vaults. Build one with a comfortable
// subscription window; LoanBrokerSet itself is not phase-gated,
// so leaving the vault in the Subscription phase is fine here.
// Create vault
uint256 vaultID;
Vault const vault{env};
auto [tx, vKeylet, _] = vault.createClosedEnded(
{.owner = a,
.asset = asset,
.subscriptionOffset = std::chrono::seconds{60},
.investmentWindow = std::chrono::seconds{kMinInvestmentPeriod + 1'000'000u}});
auto [tx, vKeylet] = vault.create({.owner = a, .asset = asset});
env(tx);
BEAST_EXPECT(env.le(vKeylet));
@@ -2887,16 +2887,8 @@ class InvariantsVault_test : public InvariantsBase
env(pay(issuer, borrower, usd(1'000)));
env.close();
// Under featureLendingProtocolV1_1 LoanBrokerSet::preclaim only
// accepts closed-ended vaults. The 10-year investment window
// covers this helper's 120 monthly payments so LoanSet's
// RedemptionDate bound is satisfied.
Vault const vault{env};
auto [vaultTx, vaultKeylet, subscriptionDate] = vault.createClosedEnded(
{.owner = owner,
.asset = usd,
.subscriptionOffset = std::chrono::seconds{60},
.investmentWindow = std::chrono::seconds{10ull * 365ull * 24ull * 60ull * 60ull}});
auto [vaultTx, vaultKeylet] = vault.create({.owner = owner, .asset = usd});
env(vaultTx);
env.close();
@@ -2920,9 +2912,6 @@ class InvariantsVault_test : public InvariantsBase
env.close();
}
// LoanSet is gated on Investment; advance out of Subscription.
vault.closePastSubscription(subscriptionDate);
auto const brokerSle = env.le(brokerKeylet);
if (!BEAST_EXPECT(brokerSle))
return vaultKeylet;
+1 -8
View File
@@ -1901,19 +1901,12 @@ public:
env.fund(XRP(10'000), lender, borrower);
env.close();
// Under featureLendingProtocolV1_1 LoanBrokerSet::preclaim only
// accepts closed-ended vaults, so build one with a near-future
// SubscriptionDate, deposit while still in the Subscription phase,
// and advance past SubscriptionDate before creating the broker.
auto [vaultTx, vaultKeylet, subscriptionDate] =
vault.createClosedEnded({.owner = lender, .asset = xrpIssue()});
auto [vaultTx, vaultKeylet] = vault.create({.owner = lender, .asset = xrpIssue()});
env(vaultTx);
env.close();
env(vault.deposit({.depositor = lender, .id = vaultKeylet.key, .amount = XRP(1'000)}));
env.close();
vault.closePastSubscription(subscriptionDate);
auto const brokerKeylet =
keylet::loanBroker(lender.id(), SeqProxy::rawSequence(env.seq(lender)));
env(loan_broker::set(lender, vaultKeylet.key));
+15 -21
View File
@@ -72,13 +72,7 @@ class LoanBroker_test : public beast::unit_test::Suite
{
// Ensure that all the features needed for Lending Protocol are included,
// even if they are set to unsupported.
//
// featureLendingProtocolV1_1 is excluded from the default set: it adds
// the closed-ended vault gate on LoanBrokerSet::preclaim (see
// LoanBrokerSet.cpp), but this suite exercises loan-broker mechanics on
// plain open-ended vaults. Tests that specifically exercise the
// amendment opt it back in explicitly and use closed-ended vaults.
FeatureBitset const all_{jtx::testableAmendments() - featureLendingProtocolV1_1};
FeatureBitset const all_{jtx::testableAmendments()};
void
testDisabled()
@@ -878,7 +872,7 @@ class LoanBroker_test : public beast::unit_test::Suite
using namespace loan_broker;
Account const issuer{"issuer"};
Account const alice{"alice"};
Env env(*this, all_);
Env env(*this);
Vault const vault{env};
env.fund(XRP(100'000), issuer, alice);
@@ -1114,7 +1108,7 @@ class LoanBroker_test : public beast::unit_test::Suite
Account const alice{"alice"};
Account const issuer{"issuer"};
auto const usd = alice["USD"];
Env env(*this, all_);
Env env(*this);
env.fund(XRP(100'000), alice);
env.close();
@@ -1217,7 +1211,7 @@ class LoanBroker_test : public beast::unit_test::Suite
// This test is lifted directly from
// https://bugs.immunefi.com/dashboard/submission/57808
using namespace jtx;
Env env(*this, all_);
Env env(*this);
Account const alice{"alice"};
env.fund(XRP(10000), alice);
@@ -1275,7 +1269,7 @@ class LoanBroker_test : public beast::unit_test::Suite
Account const issuer{"issuer"};
Account const alice{"alice"};
Env env(*this, all_);
Env env(*this);
Vault vault{env};
env.fund(XRP(100'000), issuer, alice);
@@ -1383,7 +1377,7 @@ class LoanBroker_test : public beast::unit_test::Suite
using namespace loan_broker;
Account const issuer{"issuer"};
Account const alice{"alice"};
Env env(*this, all_);
Env env(*this);
Vault const vault{env};
env.fund(XRP(100'000), issuer, alice);
@@ -1549,7 +1543,7 @@ class LoanBroker_test : public beast::unit_test::Suite
Account const& broker = issuer;
auto test = [&](auto&& getToken) {
Env env(*this, all_);
Env env(*this);
env.fund(XRP(1'000), issuer, holder);
env.close();
@@ -1622,7 +1616,7 @@ class LoanBroker_test : public beast::unit_test::Suite
{
testcase << "RIPD-4466 - LoanBrokerSet disallows frozen vaults";
using namespace jtx;
Env env(*this, all_);
Env env(*this);
Account const issuer{"issuer"}, lender{"lender"}, borrower{"borrower"};
env.fund(XRP(20'000), issuer, lender, borrower);
@@ -1951,7 +1945,7 @@ class LoanBroker_test : public beast::unit_test::Suite
// === IOU ===
{
testcase("LoanBrokerCoverDeposit IOU freeze checks");
Env env(*this, all_);
Env env(*this);
Vault const vault{env};
env.fund(XRP(100'000), issuer, alice);
@@ -2018,7 +2012,7 @@ class LoanBroker_test : public beast::unit_test::Suite
// === MPT ===
{
testcase("LoanBrokerCoverDeposit MPT lock checks");
Env env(*this, all_);
Env env(*this);
Vault const vault{env};
env.fund(XRP(100'000), issuer, alice);
@@ -2101,7 +2095,7 @@ class LoanBroker_test : public beast::unit_test::Suite
Account const issuer{"issuer"};
Account const alice{"alice"};
Account const dest{"dest"};
Env env{*this, all_};
Env env{*this};
Vault const vault{env};
env.fund(XRP(100'000), issuer, alice, dest);
@@ -2167,7 +2161,7 @@ class LoanBroker_test : public beast::unit_test::Suite
// === IOU ===
{
testcase("LoanBrokerCoverWithdraw IOU freeze checks");
Env env(*this, all_);
Env env(*this);
Vault const vault{env};
env.fund(XRP(100'000), issuer, alice);
@@ -2279,7 +2273,7 @@ class LoanBroker_test : public beast::unit_test::Suite
// === MPT ===
{
testcase("LoanBrokerCoverWithdraw MPT lock checks");
Env env(*this, all_);
Env env(*this);
Vault const vault{env};
env.fund(XRP(100'000), issuer, alice);
@@ -2400,7 +2394,7 @@ class LoanBroker_test : public beast::unit_test::Suite
};
auto test = [&](TrustState trustState) {
Env env(*this, all_);
Env env(*this);
testcase << "RIPD-4274 IOU with state: " << static_cast<int>(trustState);
@@ -2525,7 +2519,7 @@ class LoanBroker_test : public beast::unit_test::Suite
};
auto test = [&](MPTState mptState) {
Env env(*this, all_);
Env env(*this);
testcase << "RIPD-4274 MPT with state: " << static_cast<int>(mptState);
+1 -7
View File
@@ -353,11 +353,7 @@ private:
auto const& asset = debtMaximumRequest.asset();
auto const initialVault = asset(debtMaximumRequest * 100);
// Under featureLendingProtocolV1_1 LoanBrokerSet::preclaim
// only accepts closed-ended vaults, so build one and advance
// past SubscriptionDate before creating broker/loan.
auto [tx, vaultKeylet, subscriptionDate] =
vault.createClosedEnded({.owner = broker, .asset = asset});
auto [tx, vaultKeylet] = vault.create({.owner = broker, .asset = asset});
env(tx, txFee);
env.close();
@@ -366,8 +362,6 @@ private:
txFee);
env.close();
vault.closePastSubscription(subscriptionDate);
auto const brokerKeylet =
keylet::loanBroker(broker.id(), SeqProxy::rawSequence(env.seq(broker)));
+1 -9
View File
@@ -445,17 +445,13 @@ private:
Account const depositor{"depositor"};
auto const txFee = Fee(XRP(100));
// Under featureLendingProtocolV1_1 LoanBrokerSet::preclaim only
// accepts closed-ended vaults, so build one and advance past
// SubscriptionDate before creating the broker and the loan.
Env env(*this);
Vault const vault(env);
env.fund(XRP(10'000), lender, issuer, borrower, depositor);
env.close();
auto [tx, vaultKeyLet, subscriptionDate] =
vault.createClosedEnded({.owner = lender, .asset = xrpIssue()});
auto [tx, vaultKeyLet] = vault.create({.owner = lender, .asset = xrpIssue()});
env(tx, txFee);
env.close();
@@ -463,10 +459,6 @@ private:
txFee);
env.close();
// Move into the Investment phase before creating the broker and
// the loan.
vault.closePastSubscription(subscriptionDate);
auto const brokerKeyLet =
keylet::loanBroker(lender.id(), SeqProxy::rawSequence(env.seq(lender)));
+3 -21
View File
@@ -500,27 +500,9 @@ protected:
auto const coverRateMinValue = params.coverRateMin;
// Under featureLendingProtocolV1_1 LoanBrokerSet::preclaim rejects
// brokers attached to open-ended vaults. Many callers of this
// helper leave vaultKind at the OpenEnded default and don't care
// about the vault kind per se — they just need a broker on a
// vault. When LP V1.1 is enabled, transparently promote to
// ClosedEnded so those tests keep working without threading
// vaultKind through every call site. Callers that explicitly
// asked for ClosedEnded are left untouched. Tests that want to
// exercise the open-ended rejection under LP V1.1 build their own
// vault directly instead of going through this helper, since it
// always promotes OpenEnded once the amendment is enabled.
auto effectiveVaultKind = params.vaultKind;
if (env.current()->rules().enabled(featureLendingProtocolV1_1) &&
effectiveVaultKind == VaultKind::OpenEnded)
{
effectiveVaultKind = VaultKind::ClosedEnded;
}
std::optional<std::uint32_t> subscriptionDate;
std::optional<std::uint32_t> redemptionDate;
if (effectiveVaultKind == VaultKind::ClosedEnded)
if (params.vaultKind == VaultKind::ClosedEnded)
{
auto const nowSec = env.now().time_since_epoch().count();
subscriptionDate = nowSec + params.subscriptionOffset;
@@ -531,9 +513,9 @@ protected:
{.owner = lender,
.asset = asset,
.flags = params.vaultFlags,
.vaultKind = effectiveVaultKind == VaultKind::OpenEnded
.vaultKind = params.vaultKind == VaultKind::OpenEnded
? std::optional<std::uint8_t>{}
: std::optional<std::uint8_t>{std::to_underlying(effectiveVaultKind)},
: std::optional<std::uint8_t>{std::to_underlying(params.vaultKind)},
.subscriptionDate = subscriptionDate,
.redemptionDate = redemptionDate});
if (params.vaultScale)
@@ -12,7 +12,6 @@
#include <test/jtx/ter.h>
#include <test/jtx/trust.h>
#include <test/jtx/txflags.h>
#include <test/jtx/vault.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
@@ -533,61 +532,6 @@ private:
env.close();
}
// Under featureLendingProtocolV1_1 LoanBrokerSet::preclaim rejects
// attaching a broker to an open-ended vault. VaultCreate itself is
// not gated by the amendment, so the same open-ended vault can be
// built under either feature set; only the broker create is
// amendment-sensitive. Cover both branches: LP V1.1 disabled lets
// the broker create succeed, LP V1.1 enabled rejects it. The gate
// only fires on the create path; existing brokers keep working.
void
testLoanBrokerRequiresClosedEndedVault()
{
testcase("LoanBrokerSet requires closed-ended vault under LP V1.1");
using namespace jtx;
Account const owner{"lp11_owner"};
auto const build = [&](FeatureBitset features,
TER expected,
std::optional<TER> updateExpected = std::nullopt) {
Env env(*this, features);
env.fund(XRP(1'000), owner);
env.close();
Vault const vault{env};
auto [tx, vaultKeylet] = vault.create({.owner = owner, .asset = xrpIssue()});
env(tx);
env.close();
env(vault.deposit({.depositor = owner, .id = vaultKeylet.key, .amount = XRP(100)}));
env.close();
auto const brokerKeylet =
keylet::loanBroker(owner.id(), SeqProxy::rawSequence(env.seq(owner)));
env(loan_broker::set(owner, vaultKeylet.key), Ter(expected));
env.close();
// The create-path gate is the only new check; updates to an
// existing broker on the same open-ended vault are not
// affected. Only exercise the update path when the create
// succeeded (so there is a broker to update).
if (updateExpected && expected == tesSUCCESS)
{
env(loan_broker::set(owner, vaultKeylet.key),
loan_broker::kLoanBrokerId(brokerKeylet.key),
loan_broker::kDebtMaximum(XRP(1'000).value()),
Ter(*updateExpected));
env.close();
}
};
// Baseline: LP V1.1 disabled -> open-ended vault + broker succeeds.
build(all_, tesSUCCESS, tesSUCCESS);
// LP V1.1 enabled -> open-ended vault + broker rejected on create.
build(all_ | featureLendingProtocolV1_1, tecNO_PERMISSION);
}
void
runAmendmentIndependent()
{
@@ -599,7 +543,6 @@ private:
testInvalidLoanPay();
testRequireAuth();
testLimitExceeded();
testLoanBrokerRequiresClosedEndedVault();
}
// Tests run under each entry in amendmentCombinations().
+10 -21
View File
@@ -575,49 +575,44 @@ private:
env.close();
};
// Strip featureLendingProtocolV1_1: this scenario runs an
// open-ended vault through deposit/broker/loan/repay/deposit,
// which spans both Subscription and post-loan lifetime — a phase
// pattern that only makes sense on open-ended vaults. The gate
// added by LP V1.1 is unrelated to the truncation bug asserted
// here.
auto const legacy = testableAmendments() - featureLendingProtocolV1_1;
{
testcase(
"bug: VaultDeposit share truncation lets depositor debit "
"round away to zero (pre-fixCleanup3_4_0)");
runScenario(legacy - fixCleanup3_4_0, Line::Holding, tecINVARIANT_FAILED);
runScenario(testableAmendments() - fixCleanup3_4_0, Line::Holding, tecINVARIANT_FAILED);
}
{
testcase(
"bug: VaultDeposit share truncation lets depositor debit "
"round away to zero (pre-fixCleanup3_2_0 and pre-fixCleanup3_4_0)");
runScenario(
legacy - fixCleanup3_2_0 - fixCleanup3_4_0, Line::Holding, tecINVARIANT_FAILED);
testableAmendments() - fixCleanup3_2_0 - fixCleanup3_4_0,
Line::Holding,
tecINVARIANT_FAILED);
}
{
testcase(
"bug: VaultDeposit share truncation rejected with "
"tecPRECISION_LOSS (post-fixCleanup3_4_0)");
runScenario(legacy, Line::Holding, tecPRECISION_LOSS);
runScenario(testableAmendments(), Line::Holding, tecPRECISION_LOSS);
}
{
testcase(
"bug: VaultDeposit share truncation rejected with "
"tecPRECISION_LOSS (post-fixCleanup3_4_0, pre-fixCleanup3_2_0)");
runScenario(legacy - fixCleanup3_2_0, Line::Holding, tecPRECISION_LOSS);
runScenario(testableAmendments() - fixCleanup3_2_0, Line::Holding, tecPRECISION_LOSS);
}
{
testcase(
"bug: VaultDeposit share truncation against a debt balance "
"round away to zero (pre-fixCleanup3_4_0)");
runScenario(legacy - fixCleanup3_4_0, Line::InDebt, tecINVARIANT_FAILED);
runScenario(testableAmendments() - fixCleanup3_4_0, Line::InDebt, tecINVARIANT_FAILED);
}
{
testcase(
"bug: VaultDeposit share truncation against a debt balance rejected with "
"tecPRECISION_LOSS (post-fixCleanup3_4_0)");
runScenario(legacy, Line::InDebt, tecPRECISION_LOSS);
runScenario(testableAmendments(), Line::InDebt, tecPRECISION_LOSS);
}
}
@@ -1242,10 +1237,7 @@ private:
using namespace test::jtx;
auto runScenario = [this](FeatureBitset features, bool withFix) {
// This regression requires the open-ended vault lifecycle: deposit,
// originate and repay a loan, then claw back shares. LP V1.1
// independently rejects attaching a broker to an open-ended vault.
Env env{*this, features - featureLendingProtocolV1_1};
Env env{*this, features};
auto const setup = makeRoundTripOvershootVault(env);
if (!BEAST_EXPECT(setup))
@@ -1306,10 +1298,7 @@ private:
using namespace test::jtx;
auto runScenario = [this](FeatureBitset features, bool withFix) {
// This regression requires the open-ended vault lifecycle: deposit,
// originate and repay a loan, then withdraw shares. LP V1.1
// independently rejects attaching a broker to an open-ended vault.
Env env{*this, features - featureLendingProtocolV1_1};
Env env{*this, features};
auto const setup = makeRoundTripOvershootVault(env);
if (!BEAST_EXPECT(setup))
+2 -24
View File
@@ -68,20 +68,12 @@ private:
return sleIssuance->at(sfOutstandingAmount);
};
// Under featureLendingProtocolV1_1 LoanBrokerSet::preclaim only
// accepts closed-ended vaults, so build vaults in this suite as
// closed-ended and advance past SubscriptionDate before creating
// brokers/loans. VaultClawback itself is not phase-gated. The
// subscription offset must be large enough that the deposit
// ledger close does not accidentally push us past SubscriptionDate
// (which would land the deposit in Investment phase and fail).
auto const setupVault = [&](PrettyAsset const& asset,
Account const& owner,
Account const& depositor) -> std::pair<Vault, Keylet> {
Vault const vault{env};
auto const& [tx, vaultKeylet, subscriptionDate] = vault.createClosedEnded(
{.owner = owner, .asset = asset, .subscriptionOffset = std::chrono::seconds{60}});
auto const& [tx, vaultKeylet] = vault.create({.owner = owner, .asset = asset});
env(tx, Ter(tesSUCCESS));
env.close();
@@ -95,10 +87,6 @@ private:
Ter(tesSUCCESS));
env.close();
// Move past SubscriptionDate so LoanBrokerSet/LoanSet run in
// the Investment phase.
vault.closePastSubscription(subscriptionDate);
auto const& [availablePreDefault, totalPreDefault] = vaultAssetBalance(vaultKeylet);
BEAST_EXPECT(availablePreDefault == totalPreDefault);
BEAST_EXPECT(availablePreDefault == asset(100).value());
@@ -325,21 +313,13 @@ private:
Env env(*this);
env.enableFeature(fixCleanup3_1_3);
// Under featureLendingProtocolV1_1 LoanBrokerSet::preclaim only
// accepts closed-ended vaults; some tests using this helper later
// attach loan brokers to the vault. Build it as closed-ended and
// advance past SubscriptionDate so subsequent broker/loan setup
// runs in the Investment phase. VaultClawback itself is not
// phase-gated. See the other setupVault (share tests) for why the
// subscription offset must be generous.
auto const setupVault = [&](PrettyAsset const& asset,
Account const& owner,
Account const& depositor,
Account const& issuer) -> std::pair<Vault, Keylet> {
Vault const vault{env};
auto const& [tx, vaultKeylet, subscriptionDate] = vault.createClosedEnded(
{.owner = owner, .asset = asset, .subscriptionOffset = std::chrono::seconds{60}});
auto const& [tx, vaultKeylet] = vault.create({.owner = owner, .asset = asset});
env(tx, Ter(tesSUCCESS));
env.close();
@@ -351,8 +331,6 @@ private:
Ter(tesSUCCESS));
env.close();
vault.closePastSubscription(subscriptionDate);
return std::make_pair(vault, vaultKeylet);
};
+1 -7
View File
@@ -22,7 +22,6 @@
#include <xrpl/ledger/OpenView.h>
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Keylet.h>
@@ -72,12 +71,7 @@ private:
auto testCase = [&, this](
std::uint8_t scale, std::function<void(Env & env, Data data)> test) {
// These scale-focused tests build an open-ended vault and
// exercise deposit/withdraw/clawback (with one test also
// attaching a loan broker). featureLendingProtocolV1_1 adds a
// closed-ended vault gate on LoanBrokerSet::preclaim and is
// orthogonal to what this suite asserts, so strip it here.
Env env{*this, testableAmendments() - featureLendingProtocolV1_1};
Env env{*this, testableAmendments()};
Account const owner{"owner"};
Account const issuer{"issuer"};
Account const depositor{"depositor"};
@@ -475,10 +475,7 @@ private:
"Vault withdraw: sole-shareholder partial fixed-shares uses "
"full-price rate (fixCleanup3_2_0)");
// Strip featureLendingProtocolV1_1: setupStuckDepositor builds an
// open-ended vault and this test asserts amendment-independent
// withdrawal invariants (see the note on run()).
Env env(*this, (all_ - featureLendingProtocolV1_1) | fixCleanup3_2_0);
Env env(*this, all_ | fixCleanup3_2_0);
auto const f = setupStuckDepositor(env);
if (!f.vaultKeylet || !f.asset || f.sharesLender == 0)
{
@@ -565,8 +562,7 @@ private:
"Vault withdraw: sole shareholder fully exits after impaired "
"loan is repaid (fixCleanup3_2_0)");
// Strip featureLendingProtocolV1_1 as above.
Env env(*this, (all_ - featureLendingProtocolV1_1) | fixCleanup3_2_0);
Env env(*this, all_ | fixCleanup3_2_0);
auto const f = setupStuckDepositor(env);
if (!f.vaultKeylet || !f.asset || !f.loanKeylet || f.sharesLender == 0)
{
@@ -661,20 +657,12 @@ public:
void
run() override
{
// These sole-shareholder exit scenarios build an open-ended vault
// and drive it through deposits, a loan broker, an impaired loan
// and finally a withdrawal by the last shareholder. Under
// featureLendingProtocolV1_1 LoanBrokerSet::preclaim rejects
// brokers attached to open-ended vaults, so this suite runs with
// the amendment stripped; the invariants asserted here are
// amendment-independent.
auto const legacy = all_ - featureLendingProtocolV1_1;
testWithdrawSoleShareholderFixedAssetExit(legacy - fixCleanup3_2_0);
testWithdrawSoleShareholderFixedAssetExit(legacy);
testWithdrawSoleShareholderFullSharesRejected(legacy - fixCleanup3_2_0);
testWithdrawSoleShareholderFullSharesRejected(legacy);
testWithdrawSoleShareholderCleanVaultUnaffected(legacy - fixCleanup3_2_0);
testWithdrawSoleShareholderCleanVaultUnaffected(legacy);
testWithdrawSoleShareholderFixedAssetExit(all_ - fixCleanup3_2_0);
testWithdrawSoleShareholderFixedAssetExit(all_);
testWithdrawSoleShareholderFullSharesRejected(all_ - fixCleanup3_2_0);
testWithdrawSoleShareholderFullSharesRejected(all_);
testWithdrawSoleShareholderCleanVaultUnaffected(all_ - fixCleanup3_2_0);
testWithdrawSoleShareholderCleanVaultUnaffected(all_);
testWithdrawSoleShareholderPartialFixedSharesUsesFullPrice();
testWithdrawSoleShareholderLoanRepaymentExit();
}