diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index 69127f4b57..49edb98cb8 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -1395,7 +1395,9 @@ addEmptyHolding( // If the line already exists, don't create it again. if (view.read(index)) return tecDUPLICATE; - auto const& sponsorAccountID = getTxReserveSponsorAccountID(tx); + auto const& sponsorAccountID = !isPseudoAccount(sleDst) + ? getTxReserveSponsorAccountID(tx) + : std::nullopt; return trustCreate( view, high, diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index fd2ea8e752..17cac1750b 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -22,6 +22,7 @@ #include #include +#include "test/jtx/Env.h" #include "test/jtx/ticket.h" namespace ripple { @@ -3272,6 +3273,211 @@ public: void testVault() { + 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, testable_amendments()}; + env.fund(XRP(1000000), alice, bob, gw, sponsor); + env.close(); + + Vault vault{env}; + auto [tx, keylet] = vault.create({.owner = alice, .asset = asset}); + env(tx, + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 2); // Vault, MPToken(share) + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2); + BEAST_EXPECT( + env.le(keylet)->getAccountID(sfSponsorAccount) == sponsor.id()); + } + // VaultDeposit + { + Env env{*this, testable_amendments()}; + env.fund(XRP(1000000), alice, bob, gw, sponsor); + env.close(); + + Vault 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 + + env(vault.deposit( + {.depositor = bob, .id = keylet.key, .amount = asset(100)}), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + 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) + } + // VaultWithdraw + { + // RippleState Vault + { + Env env{*this, testable_amendments()}; + env.fund(XRP(1000000), alice, bob, gw, sponsor); + env.close(); + + Vault 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(); + + env(vault.deposit( + {.depositor = bob, + .id = keylet.key, + .amount = asset(100)}), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + 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 + env(vault.withdraw( + {.depositor = bob, + .id = keylet.key, + .amount = asset(50)}), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + 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)}), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + 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, testable_amendments()}; + env.fund(XRP(1000000), alice, bob, gw, sponsor); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + Vault 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(); + + env(vault.deposit( + {.depositor = bob, .id = keylet.key, .amount = asset(100)}), + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + 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, tfSponsorReserve), + sponsor::sig(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, testable_amendments()}; + env.fund(XRP(1000000), alice, bob, gw, sponsor); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + Vault vault{env}; + auto [tx, keylet] = vault.create({.owner = alice, .asset = asset}); + env(tx, + sponsor::as(sponsor, tfSponsorReserve), + sponsor::sig(sponsor)); + env.close(); + + BEAST_EXPECT(ownerCount(env, alice) == 2); // Vault, MPToken(share) + BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2); + BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2); + + 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); + } } void @@ -3515,7 +3721,7 @@ public: testOracle(); testSignerList(); testTrustSet(); - // testVault(); + testVault(); // testXChain(); } diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp index 494f7211d1..cd0d9b1ca7 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp @@ -101,7 +101,8 @@ MPTokenIssuanceCreate::create( if (!acct) return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE - auto const sponsor = getTxReserveSponsor(view, tx); + auto const sponsor = + !isPseudoAccount((acct)) ? getTxReserveSponsor(view, tx) : std::nullopt; if (args.priorBalance) { if (auto const ret = checkInsufficientReserve( diff --git a/src/xrpld/app/tx/detail/VaultDelete.cpp b/src/xrpld/app/tx/detail/VaultDelete.cpp index 4b0d92309c..50d8ebacbf 100644 --- a/src/xrpld/app/tx/detail/VaultDelete.cpp +++ b/src/xrpld/app/tx/detail/VaultDelete.cpp @@ -166,8 +166,7 @@ VaultDelete::doApply() return tefBAD_LEDGER; // LCOV_EXCL_STOP } - auto const mptSponsor = getLedgerEntryReserveSponsor(view(), mpt); - adjustOwnerCount(view(), pseudoAcct, mptSponsor, -1, j_); + adjustOwnerCount(view(), pseudoAcct, std::nullopt, -1, j_); view().erase(mpt); diff --git a/src/xrpld/app/tx/detail/VaultDeposit.cpp b/src/xrpld/app/tx/detail/VaultDeposit.cpp index efe187fc04..1daaf27efd 100644 --- a/src/xrpld/app/tx/detail/VaultDeposit.cpp +++ b/src/xrpld/app/tx/detail/VaultDeposit.cpp @@ -302,8 +302,6 @@ VaultDeposit::doApply() if (maximum != 0 && *vault->at(sfAssetsTotal) > maximum) return tecLIMIT_EXCEEDED; - auto const sponsor = getTxReserveSponsorAccountID(ctx_.tx); - // Transfer assets from depositor to vault. if (auto const ter = accountSend( view(), @@ -311,7 +309,7 @@ VaultDeposit::doApply() vaultAccount, assetsDeposited, j_, - sponsor, + std::nullopt, WaiveTransferFee::Yes); !isTesSuccess(ter)) return ter; @@ -331,6 +329,8 @@ VaultDeposit::doApply() // LCOV_EXCL_STOP } + auto const sponsor = getTxReserveSponsorAccountID(ctx_.tx); + // Transfer shares from vault to depositor. if (auto const ter = accountSend( view(),