mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-21 19:18:22 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
440018c0fe |
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;
|
||||
|
||||
|
||||
@@ -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)));
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)));
|
||||
|
||||
|
||||
@@ -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)));
|
||||
|
||||
|
||||
@@ -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().
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user