xrpld
Loading...
Searching...
No Matches
VaultPrecisionFixture.h
1#pragma once
2
3#include <test/app/lending/LoanTestBase.h>
4#include <test/jtx/Account.h>
5#include <test/jtx/Env.h>
6#include <test/jtx/TestHelpers.h>
7#include <test/jtx/amount.h>
8#include <test/jtx/fee.h>
9#include <test/jtx/flags.h>
10#include <test/jtx/pay.h>
11#include <test/jtx/sig.h>
12#include <test/jtx/vault.h>
13
14#include <xrpl/basics/Number.h>
15#include <xrpl/basics/base_uint.h>
16#include <xrpl/basics/chrono.h>
17#include <xrpl/protocol/Asset.h>
18#include <xrpl/protocol/Feature.h>
19#include <xrpl/protocol/Indexes.h>
20#include <xrpl/protocol/Keylet.h>
21#include <xrpl/protocol/LedgerFormats.h>
22#include <xrpl/protocol/MPTIssue.h>
23#include <xrpl/protocol/Protocol.h>
24#include <xrpl/protocol/SField.h>
25#include <xrpl/protocol/STAmount.h>
26#include <xrpl/protocol/SeqProxy.h>
27#include <xrpl/protocol/TxFlags.h>
28#include <xrpl/protocol/UintTypes.h>
29#include <xrpl/protocol/Units.h>
30
31#include <cstdint>
32#include <optional>
33
34namespace xrpl::test {
35
36// Shared fixture for VaultInvariantPrecision_test and
37// VaultTransactorPrecision_test.
38// impairAndPaySibling=false: 1000 USD vault and one ordinary loan.
39// impairAndPaySibling=true: a second loan is impaired then a sibling is paid
40// off, leaving lossUnrealized at assetsTotal - assetsAvailable.
42{
43protected:
44 static constexpr std::uint32_t kFixturePaymentInterval = 86400u * 30u;
45 static constexpr std::uint32_t kFixtureGracePeriod = 86400u * 30u;
46 static constexpr std::uint32_t kFixturePaymentTotal = 120u;
47 // 10% APR, expressed in tenth-bips (1000 = 10.00 %).
48 static constexpr std::uint32_t kFixtureInterestTenthBips = 1000u;
49
50 struct Fixture
51 {
52 // Every account is initialised with a placeholder name because
53 // jtx::Account has no default constructor; setupSingleLoanVault
54 // overwrites them.
55 jtx::Account issuer{"vp_issuer_placeholder"};
56 jtx::Account lender{"vp_lender_placeholder"};
57 jtx::Account borrower{"vp_borrower_placeholder"};
58 // Distinct account used to deposit into the vault. Keeps share
59 // ownership independent of the initial vault seeding.
60 jtx::Account depositor{"vp_depositor_placeholder"};
61 // Optional so callers can BEAST_EXPECT(f.asset && f.broker)
62 // after setup; both are populated in the happy path.
65 // Keylet has no default constructor. Fill with an obviously
66 // meaningless placeholder; setupSingleLoanVault overwrites the
67 // fields that matter.
68 Keylet vaultKeylet{ltACCOUNT_ROOT, UInt256{}};
69 Keylet loan1Keylet{ltACCOUNT_ROOT, UInt256{}};
70 // Only meaningful when impairAndPaySibling == true.
71 Keylet loan2Keylet{ltACCOUNT_ROOT, UInt256{}};
72 jtx::Account vaultAccount{"vp_vault_pseudo_placeholder"};
74 };
75
76 // Read-only snapshot of the vault + share issuance at a point in time.
77 // Uses Number for exact arithmetic (no re-quantization).
78 struct Numbers
79 {
82 // The {} initializers are not redundant: Number's default constructor is explicit, so
83 // fields omitted from the designated initializer in read() below would otherwise fail
84 // copy-list-initialization.
85 // NOLINTBEGIN(readability-redundant-member-init)
86 Number assetsTotal{}; // sfAssetsTotal
87 Number assetsAvailable{}; // sfAssetsAvailable
88 Number lossUnrealized{}; // sfLossUnrealized
89 Number pseudo{}; // vault pseudo-account balance in the asset
90 Number sharesTotal{}; // sfOutstandingAmount on the share MPT
91 // NOLINTEND(readability-redundant-member-init)
92 };
93
94 static Numbers
95 read(jtx::Env const& env, Fixture const& f)
96 {
97 Numbers n{.asset = f.asset ? f.asset->raw() : Asset{}, .share = MPTIssue{f.share}};
98 if (auto const vaultSle = env.le(f.vaultKeylet))
99 {
100 n.assetsTotal = vaultSle->at(sfAssetsTotal);
101 n.assetsAvailable = vaultSle->at(sfAssetsAvailable);
102 n.lossUnrealized = vaultSle->at(sfLossUnrealized);
103 }
104 if (auto const issuanceSle = env.le(keylet::mptokenIssuance(f.share)))
105 {
106 n.sharesTotal = issuanceSle->at(sfOutstandingAmount);
107 }
108 if (f.asset)
109 n.pseudo = env.balance(f.vaultAccount, *f.asset).number();
110 return n;
111 }
112
113 // One unit at the STAmount scale of `assetsTotalAfter`. Used as the
114 // tolerance in one-unit-band assertions.
115 static Number
116 oneUnit(Asset const& asset, Number const& assetsTotalAfter)
117 {
118 return Number{1, scale(assetsTotalAfter, asset)};
119 }
120
121 // Build the shared vault + loan(s) layout. The caller constructs
122 // `env` with whatever FeatureBitset they want to exercise; this helper
123 // just uses it. If `allowClawback` is true, the issuer's
124 // asfAllowTrustLineClawback flag is set BEFORE any trust line is
125 // established for that issuer. A separate env.close() runs so the
126 // flag lands in the ledger before the trust lines are set up.
127 static Fixture
128 setupSingleLoanVault(jtx::Env& env, bool impairAndPaySibling, bool allowClawback = false)
129 {
130 using namespace jtx;
131 using namespace jtx::loan;
132 using namespace jtx::loan_broker;
133
134 Fixture f;
135 f.issuer = Account{"vp_issuer"};
136 f.lender = Account{"vp_lender"};
137 f.borrower = Account{"vp_borrower"};
138 f.depositor = Account{"vp_depositor"};
139
140 env.fund(XRP(1'000'000), f.issuer, f.lender, f.borrower, f.depositor);
141 env.close();
142
143 // Must be set BEFORE any trust line to `issuer` is created.
144 if (allowClawback)
145 {
146 env(fset(f.issuer, asfAllowTrustLineClawback));
147 env.close();
148 }
149
150 PrettyAsset const asset = f.issuer["USD"];
151 f.asset = asset;
152
153 env.trust(asset(1'000'000'000), f.lender);
154 env.trust(asset(1'000'000'000), f.borrower);
155 env.trust(asset(1'000'000'000), f.depositor);
156 env(pay(f.issuer, f.lender, asset(100'000'000)));
157 env(pay(f.issuer, f.borrower, asset(100'000'000)));
158 env(pay(f.issuer, f.depositor, asset(100'000'000)));
159 env.close();
160
161 BrokerParameters const brokerParams{
162 .vaultDeposit = 1'000,
163 .debtMax = 0,
164 .coverRateMin = percentageToTenthBips(1),
165 .coverDeposit = 10'000,
166 .managementFeeRate = TenthBips16{100},
167 .coverRateLiquidation = xrpl::lending::kMaxCoverRate};
168
169 // Build the vault + broker manually (rather than calling
170 // createVaultAndBroker) so we can seed only the lender/depositor
171 // trust lines we set up above, and skip the LoanTestBase auto
172 // funding that assumes an XRP asset.
173 Vault const vault{env};
174 auto [createTx, vaultKeylet] = vault.create({.owner = f.lender, .asset = asset});
175 env(createTx);
176 env.close();
177 f.vaultKeylet = vaultKeylet;
178
179 env(vault.deposit(
180 {.depositor = f.lender,
181 .id = vaultKeylet.key,
182 .amount = asset(brokerParams.vaultDeposit)}));
183 env.close();
184
185 auto const brokerKeylet =
187
188 env(set(f.lender, vaultKeylet.key, brokerParams.flags),
189 kManagementFeeRate(brokerParams.managementFeeRate),
190 kDebtMaximum(asset(brokerParams.debtMax).value()),
191 kCoverRateMinimum(brokerParams.coverRateMin),
192 kCoverRateLiquidation(TenthBips32(brokerParams.coverRateLiquidation)));
193 env(coverDeposit(f.lender, brokerKeylet.key, asset(brokerParams.coverDeposit).value()));
194 env.close();
195
196 f.broker = BrokerInfo{asset, brokerKeylet, vaultKeylet, brokerParams};
197
198 auto const vaultSle = env.le(vaultKeylet);
199 f.vaultAccount = Account{"vp_vault_pseudo", vaultSle->at(sfAccount)};
200 f.share = vaultSle->at(sfShareMPTID);
201
202 Fee const bigFee{env.current()->fees().base * 200};
203
204 auto const setLoan = [&](Number const& principal) -> Keylet {
205 auto const brokerSle = env.le(brokerKeylet);
206 auto const loanKeylet = keylet::loan(
207 brokerKeylet.key, SeqProxy::rawSequence(brokerSle->at(sfLoanSequence)));
208 env(loan::set(f.borrower, brokerKeylet.key, asset(principal).number()),
209 Sig(sfCounterpartySignature, f.lender),
210 jtx::loan::kInterestRate(TenthBips32{kFixtureInterestTenthBips}),
214 bigFee);
215 env.close();
216 return loanKeylet;
217 };
218
219 // Loan 1: principal 7, the one ordinary loan in both fixtures.
220 // With vault deposit 1000, this leaves A ≈ 993 (see plan).
221 f.loan1Keylet = setLoan(Number{7});
222
223 if (!impairAndPaySibling)
224 return f;
225
226 // Loan 2: sibling loan of principal 11.
227 f.loan2Keylet = setLoan(Number{11});
228
229 // Pay off loan 2 in full so its total value flows into the vault
230 // and pushes T-A upward, meeting the residual loss. Generous
231 // upper bound; the transactor takes only what is due.
232 //
233 // This happens before the impair below because impair under
234 // fixCleanup3_4_0 requires loan 1 to already be late, and the two
235 // loans are originated close enough together that advancing past
236 // loan 1's due date also makes loan 2 late — which would reject
237 // this full payment with tecEXPIRED.
238 auto const payoff = asset(Number{50}).value();
239 env(pay(f.borrower, f.loan2Keylet.key, payoff, tfLoanFullPayment), bigFee);
240 env.close();
241
242 // Impair loan 1 → drives sfLossUnrealized to loan 1's value.
243 if (env.current()->rules().enabled(fixCleanup3_4_0))
244 {
245 std::uint32_t const dueDate = env.le(f.loan1Keylet)->at(sfNextPaymentDueDate);
247 }
248
249 env(jtx::loan::manage(f.lender, f.loan1Keylet.key, tfLoanImpair), bigFee);
250 env.close();
251
252 return f;
253 }
254};
255
256} // namespace xrpl::test
std::chrono::time_point< NetClock > time_point
Definition chrono.h:48
std::chrono::duration< rep, period > duration
Definition chrono.h:47
Number is a floating point type that can represent a wide range of values.
Definition Number.h:351
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
Definition SeqProxy.h:62
Shared base for the Loan*_test family under src/test/app/lending/.
static Number oneUnit(Asset const &asset, Number const &assetsTotalAfter)
static Numbers read(jtx::Env const &env, Fixture const &f)
static constexpr std::uint32_t kFixtureGracePeriod
static Fixture setupSingleLoanVault(jtx::Env &env, bool impairAndPaySibling, bool allowClawback=false)
static constexpr std::uint32_t kFixtureInterestTenthBips
static constexpr std::uint32_t kFixturePaymentTotal
static constexpr std::uint32_t kFixturePaymentInterval
Immutable cryptographic account descriptor.
Definition jtx/Account.h:21
AccountID id() const
Returns the Account ID.
A transaction testing environment.
Definition Env.h:161
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
SLE::const_pointer le(Account const &account) const
Return an account root.
Definition Env.cpp:311
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:323
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:302
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition Env.cpp:201
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:354
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:377
Set the fee on a JTx.
Definition fee.h:20
Set the regular signature on a JTx.
Definition sig.h:19
Keylet loanBroker(AccountID const &owner, SeqProxy const &seq) noexcept
Definition Indexes.cpp:597
Keylet loan(UInt256 const &loanBrokerID, SeqProxy const &loanSeq) noexcept
Definition Indexes.cpp:603
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:567
constexpr TenthBips32 kMaxCoverRate
The maximum coverage rate required of a loan broker in 1/10 bips.
Definition Protocol.h:159
json::Value manage(AccountID const &account, UInt256 const &loanID, std::uint32_t flags)
json::Value set(AccountID const &account, UInt256 const &loanBrokerID, Number principalRequested, std::uint32_t flags)
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:14
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
static Number number(STAmount const &a)
Definition AMM.cpp:44
json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:15
STTx createTx(bool disabling, LedgerIndex seq, PublicKey const &txKey)
Create ttUNL_MODIFY Tx.
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
constexpr TenthBips32 percentageToTenthBips(std::uint32_t percentage)
Definition Protocol.h:127
int scale(Number const &number, Asset const &asset)
Get the scale of a Number for a given asset.
Definition STAmount.h:794
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:454
TenthBips< std::uint16_t > TenthBips16
Definition Units.h:453
BaseUInt< 256 > UInt256
Definition base_uint.h:580
BaseUInt< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
Definition UintTypes.h:54
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
UInt256 key
Definition Keylet.h:21
std::optional< jtx::PrettyAsset > asset