From a12ab0496cb450fa6ea235a612cd15d2dc4937bc Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Thu, 16 Jul 2026 11:18:26 -0400 Subject: [PATCH] fix: Add missing pseudo-account checks --- .../tx/transactors/account/AccountDelete.cpp | 2 ++ .../tx/transactors/payment/DepositPreauth.cpp | 9 +++++- src/test/app/AccountDelete_test.cpp | 24 ++++++++++++++-- src/test/app/DepositAuth_test.cpp | 28 +++++++++++++++++-- 4 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/libxrpl/tx/transactors/account/AccountDelete.cpp b/src/libxrpl/tx/transactors/account/AccountDelete.cpp index 0055fce403..ce027f4cad 100644 --- a/src/libxrpl/tx/transactors/account/AccountDelete.cpp +++ b/src/libxrpl/tx/transactors/account/AccountDelete.cpp @@ -241,6 +241,8 @@ AccountDelete::preclaim(PreclaimContext const& ctx) if (!ctx.tx.isFieldPresent(sfCredentialIDs)) { // Check whether the destination account requires deposit authorization. + // This also checks if destination is a pseudo-account, since pseudo-accounts have the + // lsfDepositAuth flag set by default if (sleDst->isFlag(lsfDepositAuth)) { if (!ctx.view.exists(keylet::depositPreauth(dst, account))) diff --git a/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp index d3e2af86ef..aa2c6e42bd 100644 --- a/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp +++ b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp @@ -103,9 +103,16 @@ DepositPreauth::preclaim(PreclaimContext const& ctx) { // Verify that the Authorize account is present in the ledger. AccountID const auth{ctx.tx[sfAuthorize]}; - if (!ctx.view.exists(keylet::account(auth))) + auto const sleAuth = ctx.view.read(keylet::account(auth)); + if (!sleAuth) return tecNO_TARGET; + if (ctx.view.rules().enabled(fixCleanup3_3_0) && isPseudoAccount(sleAuth)) + { + JLOG(ctx.j.debug()) << "Authorized account is a pseudo-account."; + return tecNO_PERMISSION; + } + // Verify that the Preauth entry they asked to add is not already // in the ledger. if (ctx.view.exists(keylet::depositPreauth(account, auth))) diff --git a/src/test/app/AccountDelete_test.cpp b/src/test/app/AccountDelete_test.cpp index 399696ec0d..8fbb786caf 100644 --- a/src/test/app/AccountDelete_test.cpp +++ b/src/test/app/AccountDelete_test.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -31,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -687,7 +689,7 @@ public: } void - testDest() + testDest(FeatureBitset features) { testcase("Destination Constraints"); @@ -698,7 +700,7 @@ public: Account const carol{"carol"}; Account const daria{"daria"}; - Env env{*this}; + Env env{*this, features}; env.fund(XRP(100000), alice, becky, carol); env.close(); @@ -711,6 +713,16 @@ public: env(fset(carol, asfRequireDest)); env.close(); + // Need to create a pseudo-account + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = alice, .asset = xrpIssue()}); + env(tx); + env.close(); + auto const sleVault = env.le(keylet); + if (!BEAST_EXPECT(sleVault)) + return; + Account const vaultPseudo{"vaultPseudo", sleVault->at(sfAccount)}; + // Close enough ledgers to be able to delete becky's account. incLgrSeqForAccDel(env, becky); @@ -730,6 +742,10 @@ public: env(acctdelete(becky, alice), Fee(acctDelFee), Ter(tecNO_PERMISSION)); env.close(); + // becky attempts to delete her account using a pseudo-account as the + // destination, which fails since pseudo-accounts have deposit auth enabled. + env(acctdelete(becky, vaultPseudo), Fee(acctDelFee), Ter(tecNO_PERMISSION)); + // alice preauthorizes deposits from becky. Now becky can delete her // account and forward the leftovers to alice. env(deposit::auth(alice, becky)); @@ -1076,6 +1092,7 @@ public: void run() override { + auto const all{jtx::testableAmendments()}; testBasics(); testDirectories(); testOwnedTypes(); @@ -1083,7 +1100,8 @@ public: testImplicitlyCreatedTrustline(); testBalanceTooSmallForFee(); testWithTickets(); - testDest(); + testDest(all); + testDest(all - fixCleanup3_3_0); testDestinationDepositAuthCredentials(); testDeleteCredentialsOwner(); } diff --git a/src/test/app/DepositAuth_test.cpp b/src/test/app/DepositAuth_test.cpp index c75bdeaf3a..5943f5ca95 100644 --- a/src/test/app/DepositAuth_test.cpp +++ b/src/test/app/DepositAuth_test.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -28,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -444,7 +446,7 @@ struct DepositPreauth_test : public beast::unit_test::Suite } void - testInvalid() + testInvalid(FeatureBitset features) { testcase("Invalid"); @@ -453,7 +455,7 @@ struct DepositPreauth_test : public beast::unit_test::Suite Account const becky{"becky"}; Account const carol{"carol"}; - Env env(*this); + Env env(*this, features); // Tell env about alice, becky and carol since they are not yet funded. env.memoize(alice); @@ -559,6 +561,25 @@ struct DepositPreauth_test : public beast::unit_test::Suite env.close(); env.require(Owners(alice, 0)); env.require(Owners(becky, 0)); + + { + // alice attempts to authorize a pseudo-account. + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = becky, .asset = xrpIssue()}); + env(tx); + env.close(); + + auto const sleVault = env.le(keylet); + if (!BEAST_EXPECT(sleVault)) + return; + Account const vaultPseudo{"vault", sleVault->at(sfAccount)}; + + auto const expectedResult = + features[fixCleanup3_3_0] ? Ter(tecNO_PERMISSION) : Ter(tesSUCCESS); + env(deposit::auth(alice, vaultPseudo), expectedResult); + env.close(); + env.require(Owners(alice, features[fixCleanup3_3_0] ? 0 : 1)); + } } void @@ -1419,8 +1440,9 @@ struct DepositPreauth_test : public beast::unit_test::Suite run() override { testEnable(); - testInvalid(); auto const supported{jtx::testableAmendments()}; + testInvalid(supported); + testInvalid(supported - fixCleanup3_3_0); testPayment(supported - featureCredentials); testPayment(supported); testCredentialsPayment();