test Sponsor Reserve checks for TrustSet

This commit is contained in:
tequ
2025-09-24 19:05:22 +09:00
parent 699297f01a
commit d5a1314c47
4 changed files with 123 additions and 62 deletions

View File

@@ -451,6 +451,9 @@ areCompatible(
beast::Journal::Stream& s,
char const* reason);
uint32_t
ownerCount(std::shared_ptr<SLE const> const& sponsorSle);
TER
checkInsufficientReserve(
ReadView const& view,

View File

@@ -1029,6 +1029,18 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal)
return std::nullopt;
}
uint32_t
ownerCount(std::shared_ptr<SLE const> const& sponsorSle)
{
auto const ownerCount = sponsorSle->getFieldU32(sfOwnerCount);
auto const sponsoredOwnerCount =
sponsorSle->getFieldU32(sfSponsoredOwnerCount);
auto const sponsoringOwnerCount =
sponsorSle->getFieldU32(sfSponsoringOwnerCount);
return ownerCount + sponsoringOwnerCount - sponsoredOwnerCount;
}
TER
checkInsufficientReserve(
ReadView const& view,
@@ -1479,7 +1491,7 @@ authorizeMPToken(
// an account owns, in the case of MPTokens we only
// *enforce* a reserve if the user owns more than two
// items. This is similar to the reserve requirements of trust lines.
if (sleAcct->getFieldU32(sfOwnerCount) >= 2)
if (ownerCount(sponsor.value_or(sleAcct)) >= 2)
{
if (auto const ret = checkInsufficientReserve(
view, sleAcct, priorBalance, sponsor, 1);

View File

@@ -47,10 +47,10 @@ adjustAccountXRPBalance(
return;
if (currentBalance > balanceTo)
env(pay(account, env.master, currentBalance - balanceTo + XRP(1)),
env(pay(account, env.master, currentBalance - (balanceTo + XRP(1))),
fee(XRP(1)));
else
env(pay(env.master, account, balanceTo - currentBalance), fee(XRP(1)));
env(pay(env.master, account, balanceTo - currentBalance));
env.close();
}
@@ -1968,10 +1968,13 @@ public:
env.close();
// for free mptoken checks
std::uint32_t bobTicketSeq{env.seq(bob) + 1};
env(ticket::create(bob, 2));
adjustAccountXRPBalance(env, sponsor, reserve(env, 2));
std::uint32_t ticketSeq{env.seq(sponsor) + 1};
env(ticket::create(sponsor, 2));
env.close();
adjustAccountXRPBalance(
env, sponsor, reserve(env, 3) - drops(1));
jv = {};
jv[sfTransactionType] = jss::MPTokenAuthorize;
jv[sfAccount] = bob.human();
@@ -1983,9 +1986,12 @@ public:
ter(tecINSUFFICIENT_RESERVE));
env.close();
env(noop(bob), ticket::use(bobTicketSeq));
env(noop(sponsor), ticket::use(ticketSeq));
env.close();
adjustAccountXRPBalance(
env, sponsor, reserve(env, 2) - drops(1));
// pass (free mptoken)
env(jv,
sponsor::as(sponsor, tfSponsorReserve),
@@ -2822,71 +2828,112 @@ public:
{
testcase("TrustSet");
using namespace test::jtx;
Env env{*this, testable_amendments()};
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
env.close();
auto const& highAcc = alice > bob ? alice : bob;
auto const& lowAcc = alice > bob ? bob : alice;
for (bool isIssuerHigh : {false, true})
{
auto const& issuer = isIssuerHigh ? highAcc : lowAcc;
auto const& user = isIssuerHigh ? lowAcc : highAcc;
auto const USD = issuer["USD"];
// create TrustLine
env(trust(user, USD(100)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
env.close();
BEAST_EXPECT(ownerCount(env, user) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, user) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
auto const& highAcc = alice > bob ? alice : bob;
auto const& lowAcc = alice > bob ? bob : alice;
for (bool isIssuerHigh : {false, true})
{
auto const& issuer = isIssuerHigh ? highAcc : lowAcc;
auto const& user = isIssuerHigh ? lowAcc : highAcc;
auto const line = env.le(keylet::line(user, issuer, USD.currency));
BEAST_EXPECT(
line->getAccountID(
isIssuerHigh ? sfLowSponsorAccount
: sfHighSponsorAccount) == sponsor.id());
BEAST_EXPECT(!line->isFieldPresent(
isIssuerHigh ? sfHighSponsorAccount : sfLowSponsorAccount));
auto const USD = issuer["USD"];
// transfer sponsor
env(sponsor::transfer(
user, keylet::line(user, issuer, USD.currency).key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2));
// create TrustLine
env(trust(user, USD(100)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, user) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, user) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
auto const line =
env.le(keylet::line(user, issuer, USD.currency));
BEAST_EXPECT(
line->getAccountID(
isIssuerHigh ? sfLowSponsorAccount
: sfHighSponsorAccount) == sponsor.id());
BEAST_EXPECT(!line->isFieldPresent(
isIssuerHigh ? sfHighSponsorAccount : sfLowSponsorAccount));
// transfer sponsor
env(sponsor::transfer(
user, keylet::line(user, issuer, USD.currency).key),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2));
env.close();
BEAST_EXPECT(ownerCount(env, user) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, user) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
auto const line2 =
env.le(keylet::line(user, issuer, USD.currency));
BEAST_EXPECT(
line2->getAccountID(
isIssuerHigh ? sfLowSponsorAccount
: sfHighSponsorAccount) == sponsor2.id());
BEAST_EXPECT(!line2->isFieldPresent(
isIssuerHigh ? sfHighSponsorAccount : sfLowSponsorAccount));
// delete TrustLine
env(trust(user, USD(0)));
env.close();
BEAST_EXPECT(ownerCount(env, user) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, user) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(!env.le(keylet::line(user, issuer, USD.currency)));
}
}
{
// check INSUFFICIENT_RESERVE for TrustSet
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor);
env.close();
BEAST_EXPECT(ownerCount(env, user) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, user) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
auto const& highAcc = alice > bob ? alice : bob;
auto const& lowAcc = alice > bob ? bob : alice;
for (bool isIssuerHigh : {false})
{
auto const& issuer = isIssuerHigh ? highAcc : lowAcc;
auto const& user = isIssuerHigh ? lowAcc : highAcc;
auto const USD = issuer["USD"];
auto const line2 = env.le(keylet::line(user, issuer, USD.currency));
BEAST_EXPECT(
line2->getAccountID(
isIssuerHigh ? sfLowSponsorAccount
: sfHighSponsorAccount) == sponsor2.id());
BEAST_EXPECT(!line2->isFieldPresent(
isIssuerHigh ? sfHighSponsorAccount : sfLowSponsorAccount));
adjustAccountXRPBalance(env, sponsor, reserve(env, 100));
// delete TrustLine
env(trust(user, USD(0)));
env.close();
// free trustline
std::uint32_t const ticketSeq{env.seq(sponsor) + 1};
env(ticket::create(sponsor, 2));
env.close();
BEAST_EXPECT(ownerCount(env, user) == 0);
BEAST_EXPECT(sponsoredOwnerCount(env, user) == 0);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
adjustAccountXRPBalance(
env, sponsor, reserve(env, 3) - drops(1));
BEAST_EXPECT(!env.le(keylet::line(user, issuer, USD.currency)));
// create TrustLine
env(trust(user, USD(100)),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor),
ter(tecNO_LINE_INSUF_RESERVE));
env.close();
env(noop(sponsor), ticket::use(ticketSeq));
env(noop(sponsor), ticket::use(ticketSeq + 1));
env.close();
}
}
}

View File

@@ -382,8 +382,6 @@ SetTrust::doApply()
if (!sle)
return tefINTERNAL;
std::uint32_t const uOwnerCount = sle->getFieldU32(sfOwnerCount);
// The reserve that is required to create the line. Note
// that although the reserve increases with every item
// an account owns, in the case of trust lines we only
@@ -401,7 +399,13 @@ SetTrust::doApply()
// but the incremental reserve for the trust line as
// well. A person with no intention of using the gateway
// could use the extra XRP for their own purposes.
auto const txSponsorAcc = getTxReserveSponsorAccountID(ctx_.tx);
std::optional<std::shared_ptr<SLE>> txSponsorSle = std::nullopt;
if (txSponsorAcc)
txSponsorSle = view().peek(keylet::account(*txSponsorAcc));
std::uint32_t const uOwnerCount = ownerCount(txSponsorSle.value_or(sle));
bool const freeTrustLine = uOwnerCount < 2;
std::uint32_t uQualityIn(bQualityIn ? ctx_.tx.getFieldU32(sfQualityIn) : 0);
@@ -453,11 +457,6 @@ SetTrust::doApply()
SLE::pointer sleRippleState =
view().peek(keylet::line(account_, uDstAccountID, currency));
auto const txSponsorAcc = getTxReserveSponsorAccountID(ctx_.tx);
std::optional<std::shared_ptr<SLE>> txSponsorSle = std::nullopt;
if (txSponsorAcc)
txSponsorSle = view().peek(keylet::account(*txSponsorAcc));
if (sleRippleState)
{
STAmount saLowBalance;