fix: Add missing pseudo-account checks

This commit is contained in:
Mayukha Vadari
2026-07-16 11:18:26 -04:00
committed by Ayaz Salikhov
parent 18e311e1e2
commit a12ab0496c
4 changed files with 56 additions and 7 deletions

View File

@@ -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)))

View File

@@ -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)))

View File

@@ -23,6 +23,7 @@
#include <test/jtx/ticket.h>
#include <test/jtx/trust.h>
#include <test/jtx/txflags.h>
#include <test/jtx/vault.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
@@ -31,6 +32,7 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/SField.h>
@@ -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();
}

View File

@@ -21,6 +21,7 @@
#include <test/jtx/ticket.h>
#include <test/jtx/trust.h>
#include <test/jtx/txflags.h>
#include <test/jtx/vault.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/beast/unit_test/suite.h>
@@ -28,6 +29,7 @@
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
@@ -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();