fix: Prevent stale AuthAccounts from persisting after tfTwoAssetIfEmpty re-initialization (#6996)

Co-authored-by: Bart <bthomee@users.noreply.github.com>
This commit is contained in:
Zhiyuan Wang
2026-05-09 10:43:56 -04:00
committed by Bart
parent ad2195f121
commit e1fe35993e
2 changed files with 95 additions and 0 deletions

View File

@@ -817,6 +817,9 @@ initializeFeeAuctionVote(
{
auctionSlot.makeFieldAbsent(sfDiscountedFee); // LCOV_EXCL_LINE
}
// Clear stale auth accounts from any previous auction slot holder.
if (rules.enabled(fixCleanup3_2_0) && auctionSlot.isFieldPresent(sfAuthAccounts))
auctionSlot.makeFieldAbsent(sfAuthAccounts);
}
Expected<bool, TER>

View File

@@ -23,6 +23,7 @@
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_value.h>
#include <xrpl/ledger/ApplyView.h>
@@ -7080,6 +7081,95 @@ private:
{all});
}
void
testStaleAuthAccountsAfterReinit(FeatureBitset features)
{
testcase("Test AuthAccounts reset after empty pool reinitialization");
using namespace jtx;
Env env(
*this,
envconfig([](std::unique_ptr<Config> cfg) {
cfg->FEES.reference_fee = XRPAmount(1);
return cfg;
}),
features);
Account const dan("dan");
Account const ed("ed");
fund(env, gw_, {alice_, carol_, bob_, dan, ed}, XRP(50'000), {USD(50'000)});
AMM amm(env, alice_, XRP(10'000), USD(10'000));
// Create excess trustlines to prevent AMM auto-deletion on withdrawal.
for (auto i = 0; i < kMAX_DELETABLE_AMM_TRUST_LINES + 10; ++i)
{
Account const a{std::to_string(i)};
env.fund(XRP(1'000), a);
env(trust(a, STAmount{amm.lptIssue(), 10'000}));
env.close();
}
// Carol deposits so she has LP tokens to bid.
amm.deposit(carol_, 1'000'000);
// Carol wins the auction slot, authorizing bob and dan.
env(amm.bid({
.account = carol_,
.bidMin = 100,
.authAccounts = {bob_, dan},
}));
env.close();
BEAST_EXPECT(amm.expectAuctionSlot({bob_.id(), dan.id()}));
// Withdraw all — AMM enters empty state but is not deleted because
// excess trustlines prevent auto-deletion.
amm.withdrawAll(alice_);
amm.withdrawAll(carol_);
BEAST_EXPECT(amm.ammExists());
// Pre-conditions before re-init: AMM is empty and stale sfAuthAccounts
// from carol's bid are still present.
BEAST_EXPECT(amm.getLPTokensBalance() == IOUAmount{0});
BEAST_EXPECT(amm.expectAuctionSlot({bob_.id(), dan.id()}));
// Ed re-initializes the AMM via tfTwoAssetIfEmpty with fee=500.
amm.deposit(
ed,
std::nullopt,
XRP(10'000),
USD(10'000),
std::nullopt,
tfTwoAssetIfEmpty,
std::nullopt,
std::nullopt,
500);
auto const ammSle = env.current()->read(keylet::amm(amm[0], amm[1]));
BEAST_EXPECT(ammSle && ammSle->isFieldPresent(sfAuctionSlot));
auto const& slot = safeDowncast<STObject const&>(ammSle->peekAtField(sfAuctionSlot));
// sfDiscountedFee = 500 / AUCTION_SLOT_DISCOUNTED_FEE_FRACTION = 50,
// sfPrice = 0 (reset on init), time interval = 0 (freshly issued slot).
BEAST_EXPECT(amm.expectAuctionSlot(50, 0, IOUAmount{0}));
// sfAccount must be the re-initializing depositor, not the previous
// slot holder (carol).
BEAST_EXPECT(slot[sfAccount] == ed.id());
// sfTradingFee on the AMM SLE must reflect ed's deposit fee.
BEAST_EXPECT(ammSle->getFieldU16(sfTradingFee) == 500);
// sfVoteSlots must be reset to a single entry for ed.
auto const& votes = ammSle->getFieldArray(sfVoteSlots);
BEAST_EXPECT(votes.size() == 1);
if (!votes.empty())
{
BEAST_EXPECT(votes[0].getAccountID(sfAccount) == ed.id());
BEAST_EXPECT(votes[0].getFieldU16(sfTradingFee) == 500);
BEAST_EXPECT(votes[0].getFieldU32(sfVoteWeight) == kVOTE_WEIGHT_SCALE_FACTOR);
}
// sfAuthAccounts behaviour depends on the fix.
if (features[fixCleanup3_2_0])
{
BEAST_EXPECT(!slot.isFieldPresent(sfAuthAccounts));
}
else
{
BEAST_EXPECT(
slot.isFieldPresent(sfAuthAccounts) && !slot.getFieldArray(sfAuthAccounts).empty());
}
}
void
run() override
{
@@ -7150,6 +7240,8 @@ private:
testWithdrawRounding(all);
testWithdrawRounding(all - fixAMMv1_3);
testFailedPseudoAccount();
testStaleAuthAccountsAfterReinit(all);
testStaleAuthAccountsAfterReinit(all - fixCleanup3_2_0);
}
};