test: Re-home the credential pin tests into the vault bug suite

develop split Vault_test.cpp into topical suites under src/test/app/vault/, which removed the file
these two regression tests lived in. They cover a bug, so they follow the neighbouring bug tests
into VaultBugs_test.cpp; the suite's includes grow to cover ownerCount, the credentials helpers,
keylet::account and std::uint16_t, none of which the file used before.
This commit is contained in:
Timur Ialymov
2026-08-19 15:40:29 +01:00
parent 76d9db1f1d
commit 27951da623

View File

@@ -2,7 +2,9 @@
#include <test/jtx/Account.h>
#include <test/jtx/CaptureLogs.h>
#include <test/jtx/Env.h>
#include <test/jtx/TestHelpers.h>
#include <test/jtx/amount.h>
#include <test/jtx/credentials.h>
#include <test/jtx/flags.h>
#include <test/jtx/pay.h>
#include <test/jtx/ter.h>
@@ -15,6 +17,7 @@
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/SField.h>
@@ -22,6 +25,7 @@
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <cstdint>
#include <memory>
#include <string>
#include <tuple>
@@ -792,6 +796,117 @@ private:
}
}
void
testCredentialPinsPseudoAccount()
{
using namespace test::jtx;
// A credential issued to a vault pseudo-account can't be accepted or
// deleted by it (pseudo-accounts can't sign), so it stays pinned in the
// pseudo-account's owner directory and blocks VaultDelete with
// tecHAS_OBLIGATIONS. A pin created before the cure activates is removed
// by VaultDelete once it does.
Account const owner{"owner"};
Account const attacker{"attacker"};
char const credType[] = "FN36";
Env env{*this, all_ - fixCleanup3_3_0 - fixCleanup3_4_0};
env.fund(XRP(1'000'000), owner, attacker);
env.close();
Vault const vault{env};
PrettyAsset const asset = xrpIssue();
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
auto const vaultSle = env.le(keylet);
BEAST_EXPECT(vaultSle);
Account const pseudo{"vault pseudo-account", vaultSle->at(sfAccount)};
env.memoize(pseudo);
// The pseudo-account owns the share issuance; the pin must not change
// its owner count (an unaccepted credential is owned by the issuer).
auto const pseudoOwnerCount = ownerCount(env, pseudo);
testcase("Credential pins vault pseudo-account");
env(credentials::create(pseudo, attacker, credType));
env.close();
auto const credKey = credentials::keylet(pseudo, attacker, credType);
BEAST_EXPECT(env.le(credKey));
BEAST_EXPECT(ownerCount(env, attacker) == 1);
BEAST_EXPECT(ownerCount(env, pseudo) == pseudoOwnerCount);
// The pin blocks deletion of an otherwise-empty vault.
env(vault.del({.owner = owner, .id = keylet.key}), Ter(tecHAS_OBLIGATIONS));
env.close();
env.enableFeature(fixCleanup3_4_0);
env.close();
// The pre-existing pin no longer blocks deletion; the credential is
// cleaned up and the issuer's owner count is restored.
testcase("VaultDelete removes pinned credential");
env(vault.del({.owner = owner, .id = keylet.key}));
env.close();
BEAST_EXPECT(!env.le(credKey));
BEAST_EXPECT(!env.le(keylet));
BEAST_EXPECT(!env.le(::xrpl::keylet::account(pseudo.id())));
BEAST_EXPECT(ownerCount(env, attacker) == 0);
}
void
testCredentialPinOverflow()
{
using namespace test::jtx;
testcase("Credential pin cleanup is bounded (tecINCOMPLETE)");
// A pseudo-account can be pinned with more credentials than one
// transaction is allowed to clean up. VaultDelete then removes them a
// bounded batch at a time, returning tecINCOMPLETE until the last batch.
Account const owner{"owner"};
Account const attacker{"attacker"};
Env env{*this, all_ - fixCleanup3_3_0 - fixCleanup3_4_0};
env.fund(XRP(10'000'000), owner, attacker);
env.close();
Vault const vault{env};
auto [tx, keylet] = vault.create({.owner = owner, .asset = xrpIssue()});
env(tx);
env.close();
auto const vaultSle = env.le(keylet);
BEAST_EXPECT(vaultSle);
Account const pseudo{"vault pseudo-account", vaultSle->at(sfAccount)};
env.memoize(pseudo);
// Pin more than one cleanup batch's worth of credentials.
std::uint16_t const count = kMaxDeletablePseudoAccountCredentials + 3;
for (std::uint16_t i = 0; i < count; ++i)
env(credentials::create(pseudo, attacker, std::to_string(i)));
env.close();
BEAST_EXPECT(ownerCount(env, attacker) == count);
env.enableFeature(fixCleanup3_4_0);
env.close();
// First delete removes one bounded batch and reports it isn't finished.
env(vault.del({.owner = owner, .id = keylet.key}), Ter(tecINCOMPLETE));
env.close();
BEAST_EXPECT(env.le(keylet)); // vault still exists
auto const remaining = ownerCount(env, attacker);
BEAST_EXPECT(remaining > 0 && remaining < count);
// Second delete finishes the cleanup and removes the vault.
env(vault.del({.owner = owner, .id = keylet.key}));
env.close();
BEAST_EXPECT(!env.le(keylet));
BEAST_EXPECT(!env.le(::xrpl::keylet::account(pseudo.id())));
BEAST_EXPECT(ownerCount(env, attacker) == 0);
}
public:
void
run() override
@@ -804,6 +919,8 @@ public:
testVaultWithdrawCanonicalizeToZero();
testBugVaultDustDebitCanonicalizesToNoOp();
testVaultDepositNegativeBalanceFromOppositeLimit();
testCredentialPinsPseudoAccount();
testCredentialPinOverflow();
testBug6LimitBypassWithShares();
}
};