1#include <test/app/vault/VaultTestBase.h>
2#include <test/jtx/Account.h>
3#include <test/jtx/CaptureLogs.h>
4#include <test/jtx/Env.h>
5#include <test/jtx/TestHelpers.h>
6#include <test/jtx/amount.h>
7#include <test/jtx/fee.h>
8#include <test/jtx/pay.h>
9#include <test/jtx/sig.h>
10#include <test/jtx/ter.h>
11#include <test/jtx/trust.h>
12#include <test/jtx/vault.h>
14#include <xrpl/basics/Number.h>
15#include <xrpl/basics/base_uint.h>
16#include <xrpl/basics/chrono.h>
17#include <xrpl/beast/unit_test/suite.h>
18#include <xrpl/beast/utility/Zero.h>
19#include <xrpl/json/json_forwards.h>
20#include <xrpl/json/json_value.h>
21#include <xrpl/protocol/Asset.h>
22#include <xrpl/protocol/Feature.h>
23#include <xrpl/protocol/Indexes.h>
24#include <xrpl/protocol/Keylet.h>
25#include <xrpl/protocol/MPTIssue.h>
26#include <xrpl/protocol/SField.h>
27#include <xrpl/protocol/STAmount.h>
28#include <xrpl/protocol/SeqProxy.h>
29#include <xrpl/protocol/TER.h>
30#include <xrpl/protocol/TxFlags.h>
31#include <xrpl/protocol/UintTypes.h>
32#include <xrpl/protocol/XRPAmount.h>
85 env(trust(f.
bob, (*f.
asset)(10'000'000)));
97 auto [createTx, vaultKeylet] = v.create({.owner = f.
lender, .asset = *f.
asset});
100 if (!BEAST_EXPECT(env.
le(vaultKeylet)))
105 .depositor = f.lender,
106 .id = vaultKeylet.key,
107 .amount = (*f.asset)(kStuckDeposit),
112 .id = vaultKeylet.key,
113 .amount = (*f.asset)(kStuckDeposit),
122 using namespace loan_broker;
130 if (!BEAST_EXPECT(sleBroker))
136 using namespace loan;
137 using namespace std::chrono_literals;
139 Sig(sfCounterpartySignature, f.
lender),
142 Fee(env.
current()->fees().base * 2),
149 if (!BEAST_EXPECT(loanSle))
151 std::uint32_t const dueDate = loanSle->at(sfNextPaymentDueDate);
158 auto const vaultSle = env.
le(vaultKeylet);
159 if (!BEAST_EXPECT(vaultSle))
166 if (!BEAST_EXPECT(tokenBob))
168 std::uint64_t const sharesBob = tokenBob->getFieldU64(sfMPTAmount);
174 .id = vaultKeylet.key,
175 .amount = bobShareAmt,
181 if (!BEAST_EXPECT(tokenLender))
186 if (!BEAST_EXPECT(sleIssuance))
188 BEAST_EXPECT(sleIssuance->getFieldU64(sfOutstandingAmount) == f.
sharesLender);
190 auto const vaultAfterBob = env.
le(vaultKeylet);
191 if (!BEAST_EXPECT(vaultAfterBob))
198 vaultAfterBob->at(sfAssetsTotal) - vaultAfterBob->at(sfAssetsAvailable) ==
199 vaultAfterBob->at(sfLossUnrealized));
216 bool const withFix = features[fixCleanup3_2_0];
218 std::string{
"Vault withdraw: sole shareholder exits via "
219 "fixed-asset amount with impaired loan"} +
220 (withFix ?
" (fixCleanup3_2_0)" :
" (pre-fix)"));
225 if (!f.vaultKeylet || !f.asset || f.sharesLender == 0)
230 Keylet const& vaultKey = *f.vaultKeylet;
233 auto const vaultBefore = env.le(vaultKey);
234 if (!BEAST_EXPECT(vaultBefore))
236 Number const availableBefore = vaultBefore->at(sfAssetsAvailable);
237 Number const totalBefore = vaultBefore->at(sfAssetsTotal);
238 Number const lossBefore = vaultBefore->at(sfLossUnrealized);
240 STAmount const lenderBalanceBefore = env.balance(f.lender, asset);
262 withFix ? asset(1000).value() :
STAmount{asset.
raw(), availableBefore};
265 .depositor = f.lender,
267 .amount = requestAssets,
272 auto const vaultAfter = env.le(vaultKey);
273 if (!BEAST_EXPECT(vaultAfter))
276 if (!BEAST_EXPECT(issuanceAfter))
279 std::uint64_t const sharesAfter = issuanceAfter->getFieldU64(sfOutstandingAmount);
280 Number const availableAfter = vaultAfter->at(sfAssetsAvailable);
281 Number const totalAfter = vaultAfter->at(sfAssetsTotal);
282 Number const lossAfter = vaultAfter->at(sfLossUnrealized);
287 BEAST_EXPECT(sharesAfter == f.sharesLender);
288 BEAST_EXPECT(availableAfter == availableBefore);
289 BEAST_EXPECT(totalAfter == totalBefore);
290 BEAST_EXPECT(lossAfter == lossBefore);
301 constexpr std::uint64_t kExpectedSharesRedeemed = 750'018'750;
302 Number const expectedReceived =
303 totalBefore *
Number(kExpectedSharesRedeemed) /
Number(f.sharesLender);
305 BEAST_EXPECT(sharesAfter == f.sharesLender - kExpectedSharesRedeemed);
308 BEAST_EXPECT(lossAfter == lossBefore);
312 BEAST_EXPECT(totalAfter - availableAfter == lossAfter);
314 STAmount const lenderBalanceAfter = env.balance(f.lender, asset);
315 Number const received{lenderBalanceAfter - lenderBalanceBefore};
316 BEAST_EXPECT(received == expectedReceived);
320 BEAST_EXPECT(totalBefore - totalAfter == received);
321 BEAST_EXPECT(availableBefore - availableAfter == received);
335 bool const withFix = features[fixCleanup3_2_0];
337 std::string{
"Vault withdraw: sole shareholder full-shares "
338 "burn is rejected while loss outstanding"} +
339 (withFix ?
" (fixCleanup3_2_0)" :
" (pre-fix)"));
344 if (!f.vaultKeylet || f.sharesLender == 0)
349 Keylet const& vaultKey = *f.vaultKeylet;
351 auto const vaultBefore = env.le(vaultKey);
352 if (!BEAST_EXPECT(vaultBefore))
354 Number const availableBefore = vaultBefore->at(sfAssetsAvailable);
355 Number const totalBefore = vaultBefore->at(sfAssetsTotal);
356 Number const lossBefore = vaultBefore->at(sfLossUnrealized);
362 .depositor = f.lender,
370 auto const vaultAfter = env.le(vaultKey);
371 if (!BEAST_EXPECT(vaultAfter))
374 if (!BEAST_EXPECT(issuanceAfter))
376 BEAST_EXPECT(issuanceAfter->getFieldU64(sfOutstandingAmount) == f.sharesLender);
377 BEAST_EXPECT(vaultAfter->at(sfAssetsAvailable) == availableBefore);
378 BEAST_EXPECT(vaultAfter->at(sfAssetsTotal) == totalBefore);
379 BEAST_EXPECT(vaultAfter->at(sfLossUnrealized) == lossBefore);
391 bool const withFix = features[fixCleanup3_2_0];
393 std::string{
"Vault withdraw: sole shareholder clean-state "
394 "close-out unchanged"} +
395 (withFix ?
" (fixCleanup3_2_0)" :
" (pre-fix)"));
397 Env env(*
this, features);
399 Account const issuer{
"issuer"};
400 Account const lender{
"lender"};
406 env(trust(lender, asset(10'000'000)));
413 auto [createTx, vaultKeylet] = v.create({.owner = lender, .asset = asset});
419 .id = vaultKeylet.key,
420 .amount = asset(kStuckDeposit),
425 auto const vaultBefore = env.le(vaultKeylet);
426 if (!BEAST_EXPECT(vaultBefore))
428 auto const shareAsset = vaultBefore->at(sfShareMPTID);
429 auto const tokenLender = env.le(
keylet::mptoken(shareAsset, lender.id()));
430 if (!BEAST_EXPECT(tokenLender))
432 std::uint64_t const sharesLender = tokenLender->getFieldU64(sfMPTAmount);
438 .id = vaultKeylet.key,
444 auto const vaultFinal = env.le(vaultKeylet);
445 if (!BEAST_EXPECT(vaultFinal))
448 if (!BEAST_EXPECT(issuanceFinal))
450 BEAST_EXPECT(issuanceFinal->getFieldU64(sfOutstandingAmount) == 0);
451 BEAST_EXPECT(vaultFinal->at(sfAssetsTotal) ==
beast::kZero);
452 BEAST_EXPECT(vaultFinal->at(sfAssetsAvailable) ==
beast::kZero);
453 BEAST_EXPECT(vaultFinal->at(sfLossUnrealized) ==
beast::kZero);
475 "Vault withdraw: sole-shareholder partial fixed-shares uses "
476 "full-price rate (fixCleanup3_2_0)");
481 Env env(*
this, (
all_ - featureLendingProtocolV1_1) | fixCleanup3_2_0);
483 if (!f.vaultKeylet || !f.asset || f.sharesLender == 0)
488 Keylet const& vaultKey = *f.vaultKeylet;
491 auto const vaultBefore = env.le(vaultKey);
492 if (!BEAST_EXPECT(vaultBefore))
494 Number const totalBefore = vaultBefore->at(sfAssetsTotal);
495 Number const availableBefore = vaultBefore->at(sfAssetsAvailable);
496 Number const lossBefore = vaultBefore->at(sfLossUnrealized);
502 STAmount const lenderBalanceBefore = env.balance(f.lender, asset);
506 .depositor = f.lender,
517 STAmount const lenderBalanceAfter = env.balance(f.lender, asset);
518 Number const received{lenderBalanceAfter - lenderBalanceBefore};
520 BEAST_EXPECT(received == expected);
526 (totalBefore - lossBefore) *
Number(halfShares) /
Number(f.sharesLender);
527 Number const expectedDelta = lossBefore *
Number(halfShares) /
Number(f.sharesLender);
528 BEAST_EXPECT(received - discounted == expectedDelta);
530 auto const vaultAfter = env.le(vaultKey);
531 if (!BEAST_EXPECT(vaultAfter))
534 if (!BEAST_EXPECT(issuanceAfter))
541 issuanceAfter->getFieldU64(sfOutstandingAmount) == f.sharesLender - halfShares);
542 BEAST_EXPECT(vaultAfter->at(sfAssetsTotal) == totalBefore - received);
543 BEAST_EXPECT(vaultAfter->at(sfLossUnrealized) == lossBefore);
545 vaultAfter->at(sfAssetsTotal) - vaultAfter->at(sfAssetsAvailable) ==
546 vaultAfter->at(sfLossUnrealized));
549 BEAST_EXPECT(totalBefore - vaultAfter->at(sfAssetsTotal) == received);
550 BEAST_EXPECT(availableBefore - vaultAfter->at(sfAssetsAvailable) == received);
562 using namespace loan;
565 "Vault withdraw: sole shareholder fully exits after impaired "
566 "loan is repaid (fixCleanup3_2_0)");
569 Env env(*
this, (
all_ - featureLendingProtocolV1_1) | fixCleanup3_2_0);
571 if (!f.vaultKeylet || !f.asset || !f.loanKeylet || f.sharesLender == 0)
576 Keylet const& vaultKey = *f.vaultKeylet;
577 Keylet const& loanKey = *f.loanKeylet;
586 STAmount const requestAssets = asset(1000).value();
588 .depositor = f.lender,
590 .amount = requestAssets,
599 auto const tokenAfterExit = env.le(
keylet::mptoken(f.shareAsset, f.lender.id()));
600 if (!BEAST_EXPECT(tokenAfterExit))
602 std::uint64_t const retainedShares = tokenAfterExit->getFieldU64(sfMPTAmount);
603 BEAST_EXPECT(retainedShares == f.sharesLender - 750'018'750);
617 auto const vaultAfterRepay = env.le(vaultKey);
618 if (!BEAST_EXPECT(vaultAfterRepay))
623 BEAST_EXPECT(vaultAfterRepay->at(sfLossUnrealized) ==
beast::kZero);
624 BEAST_EXPECT(vaultAfterRepay->at(sfAssetsAvailable) == vaultAfterRepay->at(sfAssetsTotal));
626 STAmount const lenderBalanceBeforeFinal = env.balance(f.lender, asset);
627 Number const availableBeforeFinal = vaultAfterRepay->at(sfAssetsAvailable);
633 .depositor = f.lender,
640 auto const vaultFinal = env.le(vaultKey);
641 if (!BEAST_EXPECT(vaultFinal))
644 if (!BEAST_EXPECT(issuanceFinal))
648 BEAST_EXPECT(issuanceFinal->getFieldU64(sfOutstandingAmount) == 0);
649 BEAST_EXPECT(vaultFinal->at(sfAssetsTotal) ==
beast::kZero);
650 BEAST_EXPECT(vaultFinal->at(sfAssetsAvailable) ==
beast::kZero);
651 BEAST_EXPECT(vaultFinal->at(sfLossUnrealized) ==
beast::kZero);
655 STAmount const lenderBalanceAfter = env.balance(f.lender, asset);
656 Number const finalReceived{lenderBalanceAfter - lenderBalanceBeforeFinal};
657 BEAST_EXPECT(finalReceived == availableBeforeFinal);
671 auto const legacy =
all_ - featureLendingProtocolV1_1;
TestcaseT testcase
Memberspace for declaring test cases.
std::chrono::time_point< NetClock > time_point
std::chrono::duration< rep, period > duration
Number is a floating point type that can represent a wide range of values.
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
static constexpr std::uint32_t kStuckPayInterval
StuckDepositorFixture setupStuckDepositor(test::jtx::Env &env)
void testWithdrawSoleShareholderFullSharesRejected(FeatureBitset features)
void testWithdrawSoleShareholderCleanVaultUnaffected(FeatureBitset features)
static constexpr std::int64_t kStuckFunding
static constexpr std::int64_t kStuckDepositorIOU
static constexpr std::int64_t kStuckDeposit
void testWithdrawSoleShareholderLoanRepaymentExit()
static constexpr std::int64_t kStuckBorrowerIOU
static constexpr std::uint32_t kStuckPayTotal
static constexpr std::int64_t kStuckPrincipal
void testWithdrawSoleShareholderFixedAssetExit(FeatureBitset features)
void run() override
Runs the suite.
void testWithdrawSoleShareholderPartialFixedSharesUsesFullPrice()
Shared base for the Vault*_test family under src/test/app/vault/.
std::string const iouCurrency_
test::jtx::PrettyAsset PrettyAsset
Immutable cryptographic account descriptor.
AccountID id() const
Returns the Account ID.
A transaction testing environment.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
SLE::const_pointer le(Account const &account) const
Return an account root.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Keylet loanBroker(AccountID const &owner, SeqProxy const &seq) noexcept
Keylet loan(UInt256 const &loanBrokerID, SeqProxy const &loanSeq) noexcept
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
bool set(T &target, std::string const &name, Section const §ion)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
BaseUInt< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
TERSubset< CanCvtToTER > TER
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, xrpl)
A pair of SHAMap key and LedgerEntryType.
std::optional< Keylet > loanKeylet
test::jtx::Account borrower
test::jtx::Account lender
std::uint64_t sharesLender
test::jtx::Account issuer
std::optional< PrettyAsset > asset
std::optional< Keylet > vaultKeylet
Asset const & raw() const