Disallow fee sponsorship on Batch inner txs (#7754)

This commit is contained in:
Olek
2026-07-08 12:29:17 -04:00
committed by GitHub
parent 2cfbf728bc
commit 78e4bb17c8
2 changed files with 99 additions and 4 deletions

View File

@@ -360,6 +360,10 @@ Batch::preflight(PreflightContext const& ctx)
return temBAD_FEE;
}
// Disallow fee sponsorship on Batch inner txs
if (stx.isFieldPresent(sfSponsor) && isFeeSponsored(stx))
return temINVALID_FLAG;
auto const innerAccount = stx.getAccountID(sfAccount);
if (auto const preflightResult =
xrpl::preflight(ctx.registry, ctx.rules, parentBatchId, stx, TapBatch, ctx.j);

View File

@@ -4800,7 +4800,7 @@ public:
{
auto jt = env.jtnofill(
noop(alice),
sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee),
sponsor::As(sponsor, spfSponsorReserve),
Sig(sfSponsorSignature, sponsor));
jt.jv.removeMember(sfTxnSignature.jsonName);
jt.jv[sfSponsorSignature.jsonName].removeMember(sfTxnSignature.jsonName);
@@ -4830,8 +4830,7 @@ public:
BEAST_EXPECT(env.balance(sponsor) == XRP(900));
auto jt = env.jtnofill(
check::create(alice, bob, XRP(1)),
sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee));
check::create(alice, bob, XRP(1)), sponsor::As(sponsor, spfSponsorReserve));
// remove txn signature since it is filled by env.jtnofill()
jt.jv.removeMember(jss::TxnSignature);
@@ -4866,7 +4865,7 @@ public:
auto jt = env.jtnofill(
check::create(alice, bob, XRP(1)),
sponsor::As(sponsor, spfSponsorReserve | spfSponsorFee),
sponsor::As(sponsor, spfSponsorReserve),
Sig(sfSponsorSignature, sponsor));
// remove txn signature since it is filled by env.jtnofill()
jt.jv.removeMember(sfTxnSignature.jsonName);
@@ -4890,6 +4889,98 @@ public:
BEAST_EXPECT(env.balance(alice) == XRP(999));
BEAST_EXPECT(env.balance(sponsor) == XRP(1000));
}
// Inner tx with sfSponsor + spfSponsorFee (pre-funded) is rejected
{
Env env{*this, testableAmendments()};
env.fund(XRP(1000), alice, bob, sponsor);
env.close();
// Create pre-funded sponsorship
env(sponsor::set(sponsor, 0, 0, XRP(1)), sponsor::SponseeAcc(alice), Fee(XRP(1)));
env.close();
auto const seq = env.seq(alice);
auto const batchFee = batch::calcBatchFee(env, 0, 2);
// Create inner transaction with sponsor fee
auto innerPay = pay(alice, bob, XRP(1));
innerPay[sfSponsor.jsonName] = sponsor.human();
innerPay[sfSponsorFlags.jsonName] = static_cast<std::uint32_t>(spfSponsorFee);
// Should be rejected with temBAD_FEE
env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
batch::Inner(innerPay, seq + 1),
batch::Inner(noop(alice), seq + 2),
Ter(temINVALID_FLAG));
env.close();
}
// Inner tx with sfSponsor + spfSponsorFee (co-signed) is rejected
{
Env env{*this, testableAmendments()};
env.fund(XRP(1000), alice, bob, sponsor);
env.close();
auto const seq = env.seq(alice);
auto const batchFee = batch::calcBatchFee(env, 1, 2);
auto innerPay = pay(alice, bob, XRP(1));
innerPay[sfSponsor.jsonName] = sponsor.human();
innerPay[sfSponsorFlags.jsonName] = static_cast<std::uint32_t>(spfSponsorFee);
// Should be rejected with temBAD_FEE
env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
batch::Inner(innerPay, seq + 1),
batch::Inner(noop(alice), seq + 2),
batch::Sig(sponsor),
Ter(temINVALID_FLAG));
env.close();
}
// Inner tx with spfSponsorFee + spfSponsorReserve is rejected
{
Env env{*this, testableAmendments()};
env.fund(XRP(1000), alice, bob, sponsor);
env.close();
auto const seq = env.seq(alice);
auto const batchFee = batch::calcBatchFee(env, 0, 2);
auto innerTx = check::create(alice, bob, XRP(1));
innerTx[sfSponsor.jsonName] = sponsor.human();
innerTx[sfSponsorFlags.jsonName] =
static_cast<std::uint32_t>(spfSponsorFee | spfSponsorReserve);
// Should be rejected with temBAD_FEE (fee sponsorship check comes first)
env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
batch::Inner(innerTx, seq + 1),
batch::Inner(noop(alice), seq + 2),
Ter(temINVALID_FLAG));
env.close();
}
// Outer batch tx with sponsor fee is allowed
{
Env env{*this, testableAmendments()};
env.fund(XRP(1000), alice, bob, sponsor);
env.close();
auto const seq = env.seq(bob);
auto const batchFee = batch::calcBatchFee(env, 0, 2);
// Outer batch with sponsor fee should work fine
env(batch::outer(bob, seq, batchFee, tfAllOrNothing),
batch::Inner(noop(bob), seq + 1),
batch::Inner(noop(bob), seq + 2),
sponsor::As(sponsor, spfSponsorFee),
Sig(sfSponsorSignature, sponsor),
Ter(tesSUCCESS));
env.close();
// Sponsor paid the fee
BEAST_EXPECT(env.balance(bob) == XRP(1000));
}
}
// Verify that the central allow-list in preflight1Sponsor rejects