Remove sponsorship from vault and loan (#7628)

This commit is contained in:
Olek
2026-06-25 14:36:46 -04:00
committed by GitHub
parent ac86cdada7
commit 86484cfa39
9 changed files with 22 additions and 671 deletions

View File

@@ -184,6 +184,10 @@ LoanBrokerDelete::doApply()
return tecHAS_OBLIGATIONS; // LCOV_EXCL_LINE
}
view().erase(brokerPseudoSLE);
view().erase(broker);
{
auto owner = view().peek(keylet::account(accountID_));
if (!owner)
@@ -192,14 +196,9 @@ LoanBrokerDelete::doApply()
// Decreases the owner count by two: one for the LoanBroker object, and
// one for the pseudo-account.
// LoanBroker object can be sponsored
adjustOwnerCountObj(view(), owner, broker, -1, j_);
// pseudo-account cannot be sponsored
adjustOwnerCount(view(), owner, {}, -1, j_);
adjustOwnerCount(view(), owner, {}, -2, j_);
}
view().erase(brokerPseudoSLE);
view().erase(broker);
associateAsset(*broker, vaultAsset);
return tesSUCCESS;

View File

@@ -7,7 +7,6 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Indexes.h>
@@ -237,29 +236,11 @@ LoanBrokerSet::doApply()
if (auto const ter = dirLink(view, vaultPseudoID, broker, sfVaultNode))
return ter; // LCOV_EXCL_LINE
auto const sponsorSle = getTxReserveSponsor(view, tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkInsufficientReserve(
view, tx, owner, preFeeBalance_, {}, *sponsorSle ? 1 : 2, 0, j_);
!isTesSuccess(ret))
return ret;
if (*sponsorSle)
{
if (auto const ret = checkInsufficientReserve(
view, tx, owner, preFeeBalance_, *sponsorSle, 1, 0, j_);
!isTesSuccess(ret))
return ret;
}
// Increases the owner count by two: one for the LoanBroker object, and
// one for the pseudo-account.
// Pseudo-account cannot be sponsored
adjustOwnerCount(view, owner, {}, 1, j_);
// LoanBroker object can be sponsored
adjustOwnerCount(view, owner, *sponsorSle, 1, j_);
adjustOwnerCount(view, owner, {}, 2, j_);
if (preFeeBalance_ < accountReserve(view, owner, j_))
return tecINSUFFICIENT_RESERVE;
auto maybePseudo = createPseudoAccount(view, broker->key(), sfLoanBrokerID);
if (!maybePseudo)
@@ -289,8 +270,6 @@ LoanBrokerSet::doApply()
if (auto const coverLiq = tx[~sfCoverRateLiquidation])
broker->at(sfCoverRateLiquidation) = *coverLiq;
addSponsorToLedgerEntry(broker, *sponsorSle);
view.insert(broker);
associateAsset(*broker, vaultAsset);

View File

@@ -103,6 +103,9 @@ LoanDelete::doApply()
if (!view.dirRemove(keylet::ownerDir(borrower), loanSle->at(sfOwnerNode), loanID, false))
return tefBAD_LEDGER; // LCOV_EXCL_LINE
// Delete the Loan object
view.erase(loanSle);
// Decrement the LoanBroker's owner count.
// The broker's owner count is solely for the number of outstanding loans,
// and is distinct from the broker's pseudo-account's owner count
@@ -127,10 +130,7 @@ LoanDelete::doApply()
}
}
// Decrement the borrower's owner count
adjustOwnerCountObj(view, borrowerSle, loanSle, -1, j_);
// Delete the Loan object
view.erase(loanSle);
adjustOwnerCount(view, borrowerSle, {}, -1, j_);
// These associations shouldn't do anything, but do them just to be safe
associateAsset(*loanSle, vaultAsset);

View File

@@ -8,7 +8,6 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
@@ -513,18 +512,14 @@ LoanSet::doApply()
}
}
auto const sponsorSle = getTxReserveSponsor(view, tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
adjustOwnerCount(view, borrowerSle, {}, 1, j_);
{
auto const balance =
accountID_ == borrower ? preFeeBalance_ : borrowerSle->at(sfBalance).value().xrp();
if (auto const ret =
checkInsufficientReserve(view, tx, borrowerSle, balance, *sponsorSle, 1, 0, j_);
!isTesSuccess(ret))
return ret;
if (balance < accountReserve(view, borrowerSle, j_))
return tecINSUFFICIENT_RESERVE;
}
adjustOwnerCount(view, borrowerSle, *sponsorSle, 1, j_);
// Account for the origination fee using two payments
//
@@ -624,7 +619,6 @@ LoanSet::doApply()
loan->at(sfPreviousPaymentDueDate) = 0;
loan->at(sfNextPaymentDueDate) = startDate + paymentInterval;
loan->at(sfPaymentRemaining) = paymentTotal;
addSponsorToLedgerEntry(loan, *sponsorSle);
view.insert(loan);
// Update the balances in the vault

View File

@@ -7,7 +7,6 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
@@ -157,28 +156,9 @@ VaultCreate::doApply()
if (auto ter = dirLink(view(), accountID_, vault))
return ter;
// We will create Vault and PseudoAccount, hence increase OwnerCount by 2
auto const sponsorSle = getTxReserveSponsor(view(), tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (!ctx_.view().rules().enabled(featureSponsor))
{
adjustOwnerCount(view(), owner, *sponsorSle, 2, j_);
addSponsorToLedgerEntry(vault, *sponsorSle);
if (auto const ret =
checkInsufficientReserve(view(), tx, owner, preFeeBalance_, *sponsorSle, 0, 0, j_);
!isTesSuccess(ret))
return ret;
}
else
{
// after Sponsor Amendment, check insufficient reserve first
if (auto const ret =
checkInsufficientReserve(view(), tx, owner, preFeeBalance_, *sponsorSle, 2, 0, j_);
!isTesSuccess(ret))
return ret;
adjustOwnerCount(view(), owner, *sponsorSle, 2, j_);
addSponsorToLedgerEntry(vault, *sponsorSle);
}
adjustOwnerCount(view(), owner, {}, 2, j_);
if (preFeeBalance_ < accountReserve(view(), owner, j_))
return tecINSUFFICIENT_RESERVE;
auto maybePseudo = createPseudoAccount(view(), vault->key(), sfVaultID);
if (!maybePseudo)

View File

@@ -204,7 +204,7 @@ VaultDelete::doApply()
}
// We are destroying Vault and PseudoAccount, hence decrease by 2
adjustOwnerCountObj(view(), owner, vault, -2, j_);
adjustOwnerCount(view(), owner, {}, -2, j_);
// Destroy the vault.
view().erase(vault);

View File

@@ -6,7 +6,6 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/Feature.h>
@@ -343,19 +342,9 @@ VaultDeposit::doApply()
}
}
auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
// Transfer shares from vault to depositor.
if (auto const ter = accountSend(
view(),
vaultAccount,
accountID_,
sharesCreated,
j_,
*sponsorSle,
WaiveTransferFee::Yes);
view(), vaultAccount, accountID_, sharesCreated, j_, {}, WaiveTransferFee::Yes);
!isTesSuccess(ter))
return ter;

View File

@@ -6,7 +6,6 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -326,19 +325,10 @@ VaultWithdraw::doApply()
view().update(vault);
auto const& vaultAccount = vault->at(sfAccount);
auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
// Transfer shares from depositor to vault.
if (auto const ter = accountSend(
view(),
accountID_,
vaultAccount,
sharesRedeemed,
j_,
*sponsorSle,
WaiveTransferFee::Yes);
view(), accountID_, vaultAccount, sharesRedeemed, j_, {}, WaiveTransferFee::Yes);
!isTesSuccess(ter))
return ter;

View File

@@ -3848,584 +3848,6 @@ public:
}
}
void
testVault(bool cosigning)
{
testcase("Vault");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const gw("gw");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
Asset asset = gw["IOU"].asset();
// VaultCreate
{
Env env{*this, testableAmendments()};
env.fund(XRP(1000000), alice, bob, gw, sponsor);
env.close();
Vault const vault{env};
auto [tx, keylet] = vault.create({.owner = alice, .asset = asset});
env(ticket::create(sponsor, 2));
env.close();
testEachSponsorship(
env,
cosigning,
sponsor,
alice,
3, // Vault, PseudoAccount, MPToken(Share Token)
3,
tecINSUFFICIENT_RESERVE,
[&](Env& env, auto const& submit) {
auto result = vault.create({.owner = alice, .asset = asset});
submit(std::get<0>(result));
keylet = std::get<1>(result);
});
BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor.id());
}
// VaultDeposit
{
Env env{*this, testableAmendments()};
env.fund(XRP(1000000), alice, bob, gw, sponsor);
env.close();
Vault const vault{env};
auto [tx, keylet] = vault.create({.owner = alice, .asset = asset});
env(tx);
env.close();
env(trust(bob, asset(1000)));
env.close();
env(pay(gw, bob, asset(1000)));
env.close();
BEAST_EXPECT(ownerCount(env, bob) == 1); // RippleState
auto const depositTx =
vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(100)});
env(ticket::create(sponsor, 2)); // for free MPToken
env.close();
testEachSponsorship(
env,
cosigning,
sponsor,
bob,
1,
1,
tecINSUFFICIENT_RESERVE,
[&](Env& env, auto const& submit) { submit(depositTx); });
}
// VaultWithdraw
{
// RippleState Vault
{
Env env{*this, testableAmendments()};
env.fund(XRP(1000000), alice, bob, gw, sponsor);
env.close();
Vault const vault{env};
auto [tx, keylet] = vault.create({.owner = alice, .asset = asset});
env(tx);
env.close();
env(trust(bob, asset(100)));
env.close();
env(pay(gw, bob, asset(100)));
env.close();
auto const depositTx =
vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(100)});
env(ticket::create(sponsor, 2)); // for free MPToken
env.close();
testEachSponsorship(
env,
cosigning,
sponsor,
bob,
1,
1,
tecINSUFFICIENT_RESERVE,
[&](Env& env, auto const& submit) { submit(depositTx); });
env(trust(bob, asset(0))); // remove trustline
env.close();
BEAST_EXPECT(ownerCount(env, bob) == 1); // MPToken(share)
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); // MPToken(share)
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); // MPToken(share)
// create Trustline with vault withdraw
testEachSponsorship(
env,
cosigning,
sponsor,
bob,
1,
1,
tecNO_LINE_INSUF_RESERVE,
[&](Env& env, auto const& submit) {
submit(vault.withdraw(
{.depositor = bob, .id = keylet.key, .amount = asset(50)}));
});
BEAST_EXPECT(ownerCount(env, bob) == 2); // RippleState, MPToken(share)
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 2); // RippleState, MPToken(share)
BEAST_EXPECT(
sponsoringOwnerCount(env, sponsor) == 2); // RippleState, MPToken(share)
// remove sponsored MPToken(share)
env(vault.withdraw({.depositor = bob, .id = keylet.key, .amount = asset(50)}));
env.close();
BEAST_EXPECT(ownerCount(env, bob) == 1); // RippleState
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); // RippleState
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); // RippleState
}
// MPToken Vault
{
// VaultWithdraw doesn't create MPToken for depositor
}
}
// VaultClawback
{
// remove sponsored shares MPToken
Env env{*this, testableAmendments()};
env.fund(XRP(1000000), alice, bob, gw, sponsor);
env.close();
env(fset(gw, asfAllowTrustLineClawback));
env.close();
Vault const vault{env};
auto [tx, keylet] = vault.create({.owner = alice, .asset = asset});
env(tx);
env.close();
env(trust(bob, asset(100)));
env.close();
env(pay(gw, bob, asset(100)));
env.close();
auto const depositTx =
vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(100)});
env(ticket::create(sponsor, 2)); // for free MPToken
env.close();
testEachSponsorship(
env,
cosigning,
sponsor,
bob,
1,
1,
tecINSUFFICIENT_RESERVE,
[&](Env& env, auto const& submit) { submit(depositTx); });
BEAST_EXPECT(ownerCount(env, bob) == 2); // RippleState, MPToken(share)
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1); // MPToken(share)
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); // MPToken(share)
env(vault.clawback({.issuer = gw, .id = keylet.key, .holder = bob, .amount = asset(0)}),
sponsor::As(sponsor, spfSponsorReserve),
Sig(sfSponsorSignature, sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, bob) == 1); // RippleState
BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
// VaultDelete
{
Env env{*this, testableAmendments()};
env.fund(XRP(1000000), alice, bob, gw, sponsor);
env.close();
env(fset(gw, asfAllowTrustLineClawback));
env.close();
Vault const vault{env};
auto [tx, keylet] = vault.create({.owner = alice, .asset = asset});
env(tx, sponsor::As(sponsor, spfSponsorReserve), Sig(sfSponsorSignature, sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 3); // Vault, PseudoAccount, MPToken(share)
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 3);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 3);
env(vault.del({.owner = alice, .id = keylet.key}));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
// SponsorshipTransfer
{
Env env{*this, testableAmendments()};
env.fund(XRP(1000000), alice, bob, gw, sponsor);
env.close();
Vault const vault{env};
auto const [tx, vaultKeylet] = vault.create({.owner = alice, .asset = asset});
env(tx);
env.close();
// Alice owns the vault, pseudo account and MPToken
BEAST_EXPECT(ownerCount(env, alice) == 3);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
if (cosigning)
{
// Alice lets sponsor to sponsor her Vault
env(sponsor::transfer(alice, tfSponsorshipCreate, vaultKeylet.key),
sponsor::As(sponsor, spfSponsorReserve),
Sig(sfSponsorSignature, sponsor));
env.close();
}
else
{
// Create sponsorship with reserve count being 2 (for vault and pseudo account)
env(sponsor::set_reserve(sponsor, 0, 2), sponsor::SponseeAcc(alice));
env.close();
env(sponsor::transfer(alice, tfSponsorshipCreate, vaultKeylet.key),
sponsor::As(sponsor, spfSponsorReserve));
env.close();
auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor, alice));
if (!BEAST_EXPECT(sponsorshipSle))
return;
BEAST_EXPECT(sponsorshipSle->getFieldU32(sfRemainingOwnerCount) == 0);
}
BEAST_EXPECT(env.le(vaultKeylet)->getAccountID(sfSponsor) == sponsor.id());
BEAST_EXPECT(ownerCount(env, alice) == 3);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2);
// Vault counts for 2 reserves, vault and the pseudo account
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2);
// End sponsorship
env(sponsor::transfer(alice, tfSponsorshipEnd, vaultKeylet.key));
env.close();
BEAST_EXPECT(!env.le(vaultKeylet)->isFieldPresent(sfSponsor));
BEAST_EXPECT(ownerCount(env, alice) == 3);
// Sponsorship ended and the sponsored owner count should be 0.
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
}
}
void
testLending(bool cosigning)
{
testcase("Lending");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const issuer("issuer");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
// LoanBrokerSet / LoanBrokerDelete
{
Env env{*this, testableAmendments()};
env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
env.close();
PrettyAsset const asset{xrpIssue(), 1'000'000};
Vault const vault{env};
auto const [tx, keylet] = vault.create({.owner = alice, .asset = asset});
env(tx);
env.close();
BEAST_EXPECT(
ownerCount(env, alice) == 3); // Vault, PseudoAccount(Vault), MPToken(Vault)
// LoanBrokerSet
testEachSponsorship(
// Both the Pseudo-account and LoanBroker objects are created, but only the
// LoanBroker is sponsored.
env,
cosigning,
sponsor,
alice,
2,
1,
tecINSUFFICIENT_RESERVE,
[&](Env& env, auto const& submit) {
submit(loanBroker::set(alice, keylet.key, 0));
});
BEAST_EXPECT(
ownerCount(env, alice) ==
5); // LoanBroker, PseudoAccount(LB), (Vault, PseudoAccount(Vault), MPToken(Vault))
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice) - 1);
if (cosigning)
{
// transfer sponsor
env(sponsor::transfer(alice, tfSponsorshipReassign, brokerKeylet.key),
sponsor::As(sponsor2, spfSponsorReserve),
Sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
env.close();
// transfer sponsor
env(sponsor::transfer(alice, tfSponsorshipReassign, brokerKeylet.key),
sponsor::As(sponsor2, spfSponsorReserve));
env.close();
}
BEAST_EXPECT(
ownerCount(env, alice) ==
5); // LoanBroker, PseudoAccount(LB), (Vault, PseudoAccount(Vault), MPToken(Vault))
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
// LoanBrokerDelete
env(loanBroker::del(alice, brokerKeylet.key, 0));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 3);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
}
// LoanBrokerConverDeposit/Withdraw/Clawback
{
Env env{*this, testableAmendments()};
env.fund(XRP(1000), alice, bob, issuer, sponsor);
env.close();
MPTTester mptt{env, issuer, kMptInitNoFund};
mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
env.close();
PrettyAsset const asset = mptt["MPT"];
mptt.authorize({.account = alice});
env.close();
env(pay(issuer, alice, asset(100)));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
Vault const vault{env};
auto const [tx, keylet] = vault.create({.owner = alice, .asset = asset});
env(tx);
env.close();
env(loanBroker::set(alice, keylet.key, 0));
env.close();
BEAST_EXPECT(
ownerCount(env, alice) ==
6); // LoanBroker, PseudoAccount(LB), (Vault, PseudoAccount(Vault),
// MPToken(Vault), MPToken(issuer))
auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice) - 1);
// LoanBrokerCoverDeposit
// doesn't sponsor anything
env(loanBroker::coverDeposit(alice, brokerKeylet.key, asset(100)),
sponsor::As(sponsor, spfSponsorReserve),
Sig(sfSponsorSignature, sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 6);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
// remove MPToken(issuer)
mptt.authorize({.account = alice, .flags = tfMPTUnauthorize});
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 5);
env(ticket::create(sponsor, 2)); // for avoid free MPToken
env.close();
// LoanBrokerCoverWithdraw
testEachSponsorship(
env,
cosigning,
sponsor,
alice,
1,
1,
tecINSUFFICIENT_RESERVE,
[&](Env& env, auto const& submit) {
submit(loanBroker::coverWithdraw(alice, brokerKeylet.key, asset(10)));
});
BEAST_EXPECT(ownerCount(env, alice) == 6);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// LoanBrokerCoverClawback
// doesn't sponsor anything
env(loanBroker::coverClawback(issuer),
loanBroker::kLoanBrokerId(brokerKeylet.key),
kAmount(asset(1)),
sponsor::As(sponsor, spfSponsorReserve),
Sig(sfSponsorSignature, sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 6);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
}
// LoanSet
{
Env env{*this, testableAmendments()};
env.fund(XRP(1000000), alice, bob, issuer, sponsor, sponsor2);
env.close();
MPTTester mptt{env, issuer, kMptInitNoFund};
mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
env.close();
PrettyAsset const asset = mptt["MPT"];
mptt.authorize({.account = alice});
mptt.authorize({.account = bob});
env.close();
env(pay(issuer, alice, asset(1000)));
env(pay(issuer, bob, asset(1000)));
env.close();
Vault const vault{env};
auto const [tx, keylet] = vault.create({.owner = bob, .asset = asset});
env(tx);
env.close();
env(vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(100)}));
env.close();
auto const brokerKeylet = keylet::loanbroker(bob.id(), env.seq(bob));
env(loanBroker::set(bob, keylet.key, 0));
env.close();
env(loanBroker::coverDeposit(bob, brokerKeylet.key, asset(100)));
env.close();
auto broker = env.le(brokerKeylet);
BEAST_EXPECT(broker->getFieldU32(sfOwnerCount) == 0);
BEAST_EXPECT(!broker->isFieldPresent(sfSponsoredOwnerCount));
BEAST_EXPECT(!broker->isFieldPresent(sfSponsoringOwnerCount));
auto const loanSeq = broker->getFieldU32(sfLoanSequence);
testEachSponsorship(
env,
cosigning,
sponsor,
alice,
1,
1,
tecINSUFFICIENT_RESERVE,
[&](Env& env, auto const& submit) {
submit(
loan::set(alice, brokerKeylet.key, 10),
Sig(sfCounterpartySignature, bob),
Fee(XRP(1)));
});
broker = env.le(brokerKeylet);
// broker'object doesn't sponsored
BEAST_EXPECT(broker->getFieldU32(sfOwnerCount) == 1);
BEAST_EXPECT(!broker->isFieldPresent(sfSponsoredOwnerCount));
BEAST_EXPECT(!broker->isFieldPresent(sfSponsoringOwnerCount));
auto const loanKeylet = keylet::loan(brokerKeylet.key, loanSeq);
auto sponsorSle = env.le(keylet::account(sponsor));
BEAST_EXPECT(sponsorSle->getFieldU32(sfOwnerCount) == 0);
BEAST_EXPECT(!sponsorSle->isFieldPresent(sfSponsoredOwnerCount));
BEAST_EXPECT(sponsorSle->getFieldU32(sfSponsoringOwnerCount) == 1);
// LoanManage
env(loan::manage(bob, loanKeylet.key, lsfLoanImpaired),
sponsor::As(sponsor, spfSponsorReserve),
Sig(sfSponsorSignature, sponsor));
env.close();
// doesn't sponsor anything
sponsorSle = env.le(keylet::account(sponsor));
BEAST_EXPECT(sponsorSle->getFieldU32(sfOwnerCount) == 0);
BEAST_EXPECT(!sponsorSle->isFieldPresent(sfSponsoredOwnerCount));
BEAST_EXPECT(sponsorSle->getFieldU32(sfSponsoringOwnerCount) == 1);
// LoanPay
env(loan::pay(alice, loanKeylet.key, asset(10)),
sponsor::As(sponsor, spfSponsorReserve),
Sig(sfSponsorSignature, sponsor));
env.close();
// doesn't sponsor anything
sponsorSle = env.le(keylet::account(sponsor));
BEAST_EXPECT(sponsorSle->getFieldU32(sfOwnerCount) == 0);
BEAST_EXPECT(!sponsorSle->isFieldPresent(sfSponsoredOwnerCount));
BEAST_EXPECT(sponsorSle->getFieldU32(sfSponsoringOwnerCount) == 1);
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
// before transfer
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
if (cosigning)
{
// transfer sponsor
env(sponsor::transfer(alice, tfSponsorshipReassign, loanKeylet.key),
sponsor::As(sponsor2, spfSponsorReserve),
Sig(sfSponsorSignature, sponsor2));
env.close();
}
else
{
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
env.close();
// transfer sponsor
env(sponsor::transfer(alice, tfSponsorshipReassign, loanKeylet.key),
sponsor::As(sponsor2, spfSponsorReserve));
env.close();
}
// after transfer
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
// LoanDelete
env(loan::del(alice, loanKeylet.key),
sponsor::As(sponsor, spfSponsorReserve),
Sig(sfSponsorSignature, sponsor));
env.close();
// Sponsored ltLoan is deleted
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
// Sponsor for ltLoan object is deleted
sponsorSle = env.le(keylet::account(sponsor));
BEAST_EXPECT(sponsorSle->getFieldU32(sfOwnerCount) == 0);
BEAST_EXPECT(!sponsorSle->isFieldPresent(sfSponsoredOwnerCount));
}
}
void
testAccountDelete()
{
@@ -4963,8 +4385,6 @@ public:
testPayChan(cosigning);
testSignerList(cosigning);
testTrustSet(cosigning);
testVault(cosigning);
testLending(cosigning);
}
protected: