Compare commits

...

7 Commits

Author SHA1 Message Date
Mayukha Vadari
04987ba19d Merge branch 'develop' into copilot/apply-asfdisallowincomingtrustline 2026-07-27 10:40:02 -04:00
Mayukha Vadari
4c6ad48c90 roll back pre-commit changes 2026-07-23 16:32:48 -04:00
Mayukha Vadari
5c95d11127 respond to comments 2026-07-23 15:41:57 -04:00
Mayukha Vadari
319f71e560 Merge branch 'develop' of https://github.com/XRPLF/rippled into copilot/apply-asfdisallowincomingtrustline 2026-07-23 15:37:44 -04:00
Mayukha Vadari
0a8dcf6665 Merge branch 'develop' into copilot/apply-asfdisallowincomingtrustline 2026-07-23 12:26:02 -04:00
Mayukha Vadari
ebd33e77bb respond to comments 2026-07-23 12:22:54 -04:00
copilot-swe-agent[bot]
c59b26078d Add asfDisallowIncomingTrustline check to OfferCreate and comprehensive tests 2026-07-23 12:21:16 -04:00
2 changed files with 175 additions and 4 deletions

View File

@@ -283,10 +283,23 @@ OfferCreate::checkAcceptAsset(
return asset.visit(
[&](Issue const& issue) -> TER {
auto const& issuer = issue.getIssuer();
auto const trustLine = view.read(keylet::trustLine(id, issuer, issue.currency));
// Check if the issuer has lsfDisallowIncomingTrustline set.
// If so, the account must already have a trustline to receive tokens.
if (view.rules().enabled(fixCleanup3_4_0) &&
issuerAccount->isFlag(lsfDisallowIncomingTrustline))
{
if (!trustLine)
{
JLOG(j.debug()) << "delay: can't receive IOUs from issuer with "
"DisallowIncomingTrustline set";
return ((flags & TapRetry) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE};
}
}
if (issuerAccount->isFlag(lsfRequireAuth))
{
auto const trustLine = view.read(keylet::trustLine(id, issuer, issue.currency));
if (!trustLine)
{
return ((flags & TapRetry) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE};
@@ -309,8 +322,6 @@ OfferCreate::checkAcceptAsset(
}
}
auto const trustLine = view.read(keylet::trustLine(id, issue.account, issue.currency));
if (!trustLine)
{
return tesSUCCESS;

View File

@@ -4324,6 +4324,165 @@ public:
env.require(Balance(bob, gwUSD(10)));
}
void
testDisallowIncomingTrustline(FeatureBitset features)
{
testcase("DisallowIncomingTrustline in OfferCreate");
// Test that asfDisallowIncomingTrustline flag prevents offer crossing
// when the taker doesn't have a trustline.
//
// 1. alice creates a trustline and sells USD/gw tokens.
//
// 2. gw sets asfDisallowIncomingTrustline flag.
//
// 3. An account without a trustline tries to create an offer for USD/gw.
// Without amendment: succeeds and crosses alice's offer (backward compatible).
// With amendment: fails with tecNO_LINE (new behavior).
//
// 4. An account WITH an existing trustline can create an offer.
// The offer succeeds and crosses alice's offer.
//
// Note: The DisallowIncomingTrustline flag also prevents NEW trustlines
// from being created via TrustSet (enforced by fixDisallowIncomingV1).
// So accounts must create trustlines BEFORE the issuer sets the flag.
using namespace jtx;
auto const gw = Account("gw");
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const carol = Account("carol");
auto const dan = Account("dan");
auto const eve = Account("eve");
auto const gwUSD = gw["USD"];
// Test without fixCleanup3_4_0 amendment
{
Env env{*this, features - fixCleanup3_4_0};
env.fund(XRP(400000), gw, alice, bob);
env.close();
// Alice creates trustline and gets some USD
env(trust(alice, gwUSD(100)));
env.close();
env(pay(gw, alice, gwUSD(50)));
env.close();
// Alice creates sell offer
env(offer(alice, XRP(4000), gwUSD(40)));
env.close();
env.require(offers(alice, 1));
// GW sets DisallowIncomingTrustline flag
env(fset(gw, asfDisallowIncomingTrustline));
env.close();
// Without the amendment, bob can still create offer without trustline
// and the offer should cross (old behavior)
env(offer(bob, gwUSD(40), XRP(4000)));
env.close();
// Offer should have crossed
env.require(offers(alice, 0));
env.require(offers(bob, 0));
env.require(Balance(bob, gwUSD(40)));
}
// Test with fixCleanup3_4_0 amendment
{
Env env{*this, features};
env.fund(XRP(400000), gw, alice, bob, carol, dan);
env.close();
// Alice creates trustline and gets some USD
env(trust(alice, gwUSD(100)));
env.close();
env(pay(gw, alice, gwUSD(50)));
env.close();
// Bob and carol create trustlines BEFORE the flag is set
env(trust(bob, gwUSD(100)));
env.close();
env(trust(carol, gwUSD(100)));
env.close();
// Alice creates sell offer
env(offer(alice, XRP(4000), gwUSD(40)));
env.close();
env.require(offers(alice, 1));
env.require(Balance(alice, gwUSD(50)));
// GW sets DisallowIncomingTrustline flag
env(fset(gw, asfDisallowIncomingTrustline));
env.close();
// Dan tries to create offer without trustline - should fail
env(offer(dan, gwUSD(40), XRP(4000)), Ter(tecNO_LINE));
env.close();
// Alice's offer should still exist
env.require(offers(alice, 1));
env.require(Balance(alice, gwUSD(50)));
// Dan shouldn't have any offers or balance
env.require(offers(dan, 0));
BEAST_EXPECT(env.le(keylet::trustLine(dan, gwUSD)) == nullptr);
// Bob already has trustline, so his offer should succeed and cross
env(offer(bob, gwUSD(40), XRP(4000)));
env.close();
// Offer should have crossed
env.require(offers(alice, 0));
env.require(offers(bob, 0));
env.require(Balance(alice, gwUSD(10)));
env.require(Balance(bob, gwUSD(40)));
// Test scenario where carol already has a trustline (created before flag was set)
// Carol should be able to create offer since trustline already exists
env(pay(gw, alice, gwUSD(50)));
env.close();
env(offer(alice, XRP(1000), gwUSD(10)));
env.close();
env.require(offers(alice, 1));
env(offer(carol, gwUSD(10), XRP(1000)));
env.close();
// Offer should have crossed
env.require(offers(alice, 0));
env.require(offers(carol, 0));
env.require(Balance(alice, gwUSD(50)));
env.require(Balance(carol, gwUSD(10)));
// Test that gw can clear the flag
env(fclear(gw, asfDisallowIncomingTrustline));
env.close();
// Create new account eve without trustline
env.fund(XRP(400000), eve);
env.close();
// Bob creates another sell offer
env(pay(gw, bob, gwUSD(50)));
env.close();
env(offer(bob, XRP(5000), gwUSD(50)));
env.close();
env.require(offers(bob, 1));
// Eve should now be able to create offer without trustline (flag is cleared)
env(offer(eve, gwUSD(50), XRP(5000)));
env.close();
// Offer should have crossed
env.require(offers(bob, 0));
env.require(offers(eve, 0));
env.require(Balance(eve, gwUSD(50)));
}
}
void
testRCSmoketest(FeatureBitset features)
{
@@ -5167,6 +5326,7 @@ public:
testSelfPayUnlimitedFunds(features);
testRequireAuth(features);
testMissingAuth(features);
testDisallowIncomingTrustline(features);
testRCSmoketest(features);
testSelfAuth(features);
testDeletedOfferIssuer(features);