mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-22 06:40:53 +00:00
Co-authored-by: tequ <git@tequ.dev> Co-authored-by: yinyiqian1 <yqian@ripple.com> Co-authored-by: Mayukha Vadari <mvadari@ripple.com> Co-authored-by: Mayukha Vadari <mvadari@gmail.com> Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com> Co-authored-by: Peter Chen <34582813+PeterChen13579@users.noreply.github.com> Co-authored-by: Zhiyuan Wang <96991820+Kassaking7@users.noreply.github.com> Co-authored-by: Ayaz Salikhov <asalikhov@ripple.com> Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com> Co-authored-by: Zhiyuan Wang <1830604455@qq.com>
This commit is contained in:
@@ -1,17 +1,23 @@
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/PathSet.h>
|
||||
#include <test/jtx/TestHelpers.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/balance.h>
|
||||
#include <test/jtx/jtx_json.h>
|
||||
#include <test/jtx/offer.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/sig.h>
|
||||
#include <test/jtx/sponsor.h>
|
||||
#include <test/jtx/ter.h>
|
||||
#include <test/jtx/trust.h>
|
||||
#include <test/jtx/txflags.h>
|
||||
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ApplyViewImpl.h>
|
||||
#include <xrpl/ledger/OwnerCounts.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
@@ -19,7 +25,9 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/AmountConversions.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
@@ -236,7 +244,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite
|
||||
auto const startingAmount =
|
||||
accountHolds(pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j);
|
||||
|
||||
BEAST_EXPECT(issueIOU(pv, alice, toCredit, iss, j) == tesSUCCESS);
|
||||
BEAST_EXPECT(issueIOU(pv, alice, toCredit, iss, {}, j) == tesSUCCESS);
|
||||
BEAST_EXPECT(
|
||||
accountHolds(
|
||||
pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j) ==
|
||||
@@ -330,7 +338,7 @@ class PaymentSandbox_test : public beast::unit_test::Suite
|
||||
};
|
||||
|
||||
auto reserve = [](jtx::Env& env, std::uint32_t count) -> XRPAmount {
|
||||
return env.current()->fees().accountReserve(count);
|
||||
return env.current()->fees().accountReserve(count, 1);
|
||||
};
|
||||
|
||||
Env env(*this, features);
|
||||
@@ -389,6 +397,217 @@ class PaymentSandbox_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(balance.getIssuer() == usd.account.id());
|
||||
}
|
||||
|
||||
void
|
||||
testOwnerCountHook(FeatureBitset features)
|
||||
{
|
||||
// Test that PaymentSandbox::adjustOwnerCountHook and ownerCountHook
|
||||
// correctly track and return the maximum owner counts during a payment.
|
||||
testcase("ownerCountHook");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this, features);
|
||||
Account const alice("alice");
|
||||
Account const sponsor("sponsor");
|
||||
|
||||
env.fund(XRP(10000), alice, sponsor);
|
||||
env.close();
|
||||
|
||||
ApplyViewImpl av(&*env.current(), TapNone);
|
||||
PaymentSandbox sb(&av);
|
||||
|
||||
// Test basic owner count hook without sponsor
|
||||
{
|
||||
auto const aliceSle = sb.peek(keylet::account(alice));
|
||||
BEAST_EXPECT(aliceSle);
|
||||
|
||||
OwnerCounts const initial(aliceSle);
|
||||
OwnerCounts updated = initial;
|
||||
updated.owner = initial.owner + 2;
|
||||
|
||||
// Simulate adjusting owner count
|
||||
sb.adjustOwnerCountHook(alice, initial, updated);
|
||||
|
||||
// ownerCountHook should return the max value
|
||||
OwnerCounts const retrieved = sb.ownerCountHook(alice, initial);
|
||||
BEAST_EXPECT(retrieved.owner == updated.owner);
|
||||
BEAST_EXPECT(retrieved.sponsored == updated.sponsored);
|
||||
BEAST_EXPECT(retrieved.sponsoring == updated.sponsoring);
|
||||
}
|
||||
|
||||
// Test owner count hook with sponsor-related counts
|
||||
{
|
||||
auto const sponsorSle = sb.peek(keylet::account(sponsor));
|
||||
BEAST_EXPECT(sponsorSle);
|
||||
|
||||
OwnerCounts const sponsorInitial(sponsorSle);
|
||||
OwnerCounts sponsorUpdated = sponsorInitial;
|
||||
sponsorUpdated.owner = sponsorInitial.owner + 1;
|
||||
sponsorUpdated.sponsoring = sponsorInitial.sponsoring + 1;
|
||||
|
||||
sb.adjustOwnerCountHook(sponsor, sponsorInitial, sponsorUpdated);
|
||||
|
||||
OwnerCounts const sponsorRetrieved = sb.ownerCountHook(sponsor, sponsorInitial);
|
||||
BEAST_EXPECT(sponsorRetrieved.owner == sponsorUpdated.owner);
|
||||
BEAST_EXPECT(sponsorRetrieved.sponsoring == sponsorUpdated.sponsoring);
|
||||
}
|
||||
|
||||
// Test with stacked PaymentSandboxes
|
||||
{
|
||||
PaymentSandbox sb2(&sb);
|
||||
|
||||
auto const aliceSle = sb2.peek(keylet::account(alice));
|
||||
OwnerCounts const current(aliceSle);
|
||||
OwnerCounts further = current;
|
||||
further.owner = current.owner + 3;
|
||||
|
||||
sb2.adjustOwnerCountHook(alice, current, further);
|
||||
|
||||
// The nested sandbox should see the max from both levels
|
||||
OwnerCounts const retrieved = sb2.ownerCountHook(alice, OwnerCounts());
|
||||
BEAST_EXPECT(retrieved.owner >= further.owner);
|
||||
}
|
||||
|
||||
// Test that max logic works correctly
|
||||
{
|
||||
auto const aliceSle = sb.peek(keylet::account(alice));
|
||||
OwnerCounts const current(aliceSle);
|
||||
OwnerCounts lower = current;
|
||||
lower.owner = (current.owner > 0) ? current.owner - 1 : 0;
|
||||
|
||||
// Adjusting to a lower value
|
||||
sb.adjustOwnerCountHook(alice, current, lower);
|
||||
|
||||
// Should still return the higher value seen previously
|
||||
OwnerCounts const retrieved = sb.ownerCountHook(alice, OwnerCounts());
|
||||
BEAST_EXPECT(retrieved.owner >= lower.owner);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testOwnerCountWithTransaction(FeatureBitset features)
|
||||
{
|
||||
// Test that owner count hooks work correctly during actual transactions.
|
||||
// This verifies that when transactions modify owner counts (by creating
|
||||
// or deleting ledger objects), the hooks properly track these changes.
|
||||
testcase(
|
||||
std::string("ownerCountWithTransaction") +
|
||||
(features[featureSponsor] ? " with sponsor" : " without sponsor"));
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
auto reserve = [](jtx::Env& env, std::uint32_t count) -> XRPAmount {
|
||||
return env.current()->fees().accountReserve(count, 1);
|
||||
};
|
||||
|
||||
Env env(*this, features);
|
||||
Account const gw("gw");
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
Account const sponsor("sponsor");
|
||||
|
||||
auto const usd = gw["USD"];
|
||||
|
||||
// Fund accounts. Alice starts with exactly enough for base reserve + 2 objects
|
||||
env.fund(XRP(10000), gw, bob, sponsor);
|
||||
env.fund(reserve(env, 3) + XRP(100), alice); // Base + 2 objects + extra for fees
|
||||
env.close();
|
||||
|
||||
// Verify initial state - no owner count
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 0);
|
||||
BEAST_EXPECT(ownerCount(env, bob) == 0);
|
||||
|
||||
// Create a trust line - this increases owner count
|
||||
env(trust(alice, usd(1000)));
|
||||
env.close();
|
||||
|
||||
// alice now has 1 object (owner count = 1)
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 1);
|
||||
|
||||
// Create an offer - this further increases owner count
|
||||
env(trust(bob, usd(1000)));
|
||||
env(pay(gw, alice, usd(100)));
|
||||
env.close();
|
||||
|
||||
auto const aliceOfferSeq = env.seq(alice); // Capture the sequence before creating offer
|
||||
env(offer(alice, usd(50), XRP(50)));
|
||||
env.close();
|
||||
|
||||
// alice now has 2 objects (trust line + offer)
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 2);
|
||||
|
||||
// If sponsor feature is enabled, test sponsorship transfer
|
||||
if (features[featureSponsor])
|
||||
{
|
||||
auto const trustId = keylet::trustLine(alice, gw, usd.currency);
|
||||
BEAST_EXPECT(env.le(trustId));
|
||||
|
||||
// Transfer sponsorship - sponsor now sponsors alice's trust line
|
||||
env(sponsor::transfer(alice, tfSponsorshipCreate, trustId.key),
|
||||
sponsor::As(sponsor, spfSponsorReserve),
|
||||
Sig(sfSponsorSignature, sponsor));
|
||||
env.close();
|
||||
|
||||
// alice still has 2 objects but 1 is sponsored
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 2);
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
|
||||
// sponsor's sponsoring count should increase
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
|
||||
}
|
||||
|
||||
// Verify alice's available balance respects the reserve
|
||||
auto const aliceSle = env.le(keylet::account(alice));
|
||||
BEAST_EXPECT(aliceSle);
|
||||
|
||||
auto const aliceBalance = aliceSle->getFieldAmount(sfBalance);
|
||||
// With sponsor, 1 object is sponsored so only 1 counts for reserve
|
||||
auto const aliceReserve = reserve(env, features[featureSponsor] ? 1 : 2);
|
||||
|
||||
// alice should have limited available balance after accounting for reserve
|
||||
auto const available = aliceBalance.xrp() - aliceReserve;
|
||||
if (features[featureSponsor])
|
||||
{
|
||||
// With sponsor, alice has more available (1 sponsored object = less reserve)
|
||||
BEAST_EXPECT(available > XRP(150));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(available < XRP(150)); // Most of the balance is in reserve
|
||||
}
|
||||
|
||||
// Try to send nearly all balance - should fail due to reserve in both cases
|
||||
auto const tooMuch = aliceBalance.xrp() - XRP(1);
|
||||
env(pay(alice, bob, tooMuch), Ter(tecUNFUNDED_PAYMENT));
|
||||
env.close();
|
||||
|
||||
// Verify owner count hasn't changed
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 2);
|
||||
|
||||
// Cancel the offer - this decreases owner count
|
||||
env(offerCancel(alice, aliceOfferSeq));
|
||||
env.close();
|
||||
|
||||
// alice now has 1 object (just the trust line)
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 1);
|
||||
|
||||
if (features[featureSponsor])
|
||||
{
|
||||
// Verify sponsored count stayed the same (trust line is still sponsored)
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
|
||||
}
|
||||
|
||||
// Now alice should have more available balance (less reserve needed)
|
||||
auto const aliceSle2 = env.le(keylet::account(alice));
|
||||
auto const aliceBalance2 = aliceSle2->getFieldAmount(sfBalance);
|
||||
// With sponsor, trust line is still sponsored so 0 objects for reserve
|
||||
// Without sponsor, 1 object for reserve
|
||||
auto const aliceReserve2 = reserve(env, features[featureSponsor] ? 0 : 1);
|
||||
auto const available2 = aliceBalance2.xrp() - aliceReserve2;
|
||||
|
||||
// available2 should be greater than available (less reserve needed)
|
||||
BEAST_EXPECT(available2 > available);
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -399,11 +618,16 @@ public:
|
||||
testTinyBalance(features);
|
||||
testReserve(features);
|
||||
testBalanceHook(features);
|
||||
testOwnerCountHook(features);
|
||||
};
|
||||
using namespace jtx;
|
||||
auto const sa = testableAmendments();
|
||||
testAll(sa - featurePermissionedDEX);
|
||||
testAll(sa);
|
||||
|
||||
// Test owner count with transactions
|
||||
testOwnerCountWithTransaction(sa - featureSponsor);
|
||||
testOwnerCountWithTransaction(sa);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user