From c2ce4c79f1ab1dd6592103a9d7eac5f6b0b25109 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Sat, 5 Nov 2022 12:25:54 +0000 Subject: [PATCH] Add to allow users to opt-out of incoming Checks, Payment Channels and NFTokenOffers --- src/ripple/app/tx/impl/CreateCheck.cpp | 9 +- src/ripple/app/tx/impl/NFTokenCreateOffer.cpp | 45 +++- src/ripple/app/tx/impl/PayChan.cpp | 11 +- src/ripple/app/tx/impl/SetAccount.cpp | 37 ++++ src/ripple/protocol/Feature.h | 3 +- src/ripple/protocol/LedgerFormats.h | 9 + src/ripple/protocol/TxFlags.h | 6 + src/ripple/protocol/impl/Feature.cpp | 1 + src/test/app/Check_test.cpp | 85 ++++++++ src/test/app/NFToken_test.cpp | 122 +++++++++++ src/test/app/PayChan_test.cpp | 195 ++++++++++++------ 11 files changed, 456 insertions(+), 67 deletions(-) diff --git a/src/ripple/app/tx/impl/CreateCheck.cpp b/src/ripple/app/tx/impl/CreateCheck.cpp index a59a7c12eba..0952dc736d7 100644 --- a/src/ripple/app/tx/impl/CreateCheck.cpp +++ b/src/ripple/app/tx/impl/CreateCheck.cpp @@ -90,7 +90,14 @@ CreateCheck::preclaim(PreclaimContext const& ctx) return tecNO_DST; } - if ((sleDst->getFlags() & lsfRequireDestTag) && + uint32_t flags = sleDst->getFlags(); + + // Check if the destination has disallowed incoming checks + if (ctx.view.rules().enabled(featureDisallowIncoming) && + (flags & lsfDisallowIncomingCheck)) + return tecNO_PERMISSION; + + if ((flags & lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag)) { // The tag is basically account-specific information we don't diff --git a/src/ripple/app/tx/impl/NFTokenCreateOffer.cpp b/src/ripple/app/tx/impl/NFTokenCreateOffer.cpp index 80e4c3964a7..5d4898764e0 100644 --- a/src/ripple/app/tx/impl/NFTokenCreateOffer.cpp +++ b/src/ripple/app/tx/impl/NFTokenCreateOffer.cpp @@ -165,11 +165,46 @@ NFTokenCreateOffer::preclaim(PreclaimContext const& ctx) return tecUNFUNDED_OFFER; } - // If a destination is specified, the destination must already be in - // the ledger. - if (auto const destination = ctx.tx[~sfDestination]; - destination && !ctx.view.exists(keylet::account(*destination))) - return tecNO_DST; + auto const destination = ctx.tx[~sfDestination]; + if (destination) + { + // If a destination is specified, the destination must already be in + // the ledger. + if (!ctx.view.exists(keylet::account(*destination))) + return tecNO_DST; + + // check if the destination has disallowed incoming offers + if (ctx.view.rules().enabled(featureDisallowIncoming)) + { + // flag cannot be set unless amendment is enabled but + // out of an abundance of caution check anyway + + auto const sleDst = + ctx.view.read(keylet::account(*destination)); + + if (sleDst->getFlags() & lsfDisallowIncomingNFTOffer) + return tecNO_PERMISSION; + } + } + + auto const owner = ctx.tx[~sfOwner]; + if (owner) + { + // Check if the owner (buy offer) has disallowed incoming offers + if (ctx.view.rules().enabled(featureDisallowIncoming)) + { + auto const sleOwner = + ctx.view.read(keylet::account(*owner)); + + // defensively check + // it should not be possible to specify owner that doesn't exist + if (!sleOwner) + return tecNO_TARGET; + + if (sleOwner->getFlags() & lsfDisallowIncomingNFTOffer) + return tecNO_PERMISSION; + } + } return tesSUCCESS; } diff --git a/src/ripple/app/tx/impl/PayChan.cpp b/src/ripple/app/tx/impl/PayChan.cpp index aab3dcc5a6b..1fc3d66ffe6 100644 --- a/src/ripple/app/tx/impl/PayChan.cpp +++ b/src/ripple/app/tx/impl/PayChan.cpp @@ -217,14 +217,21 @@ PayChanCreate::preclaim(PreclaimContext const& ctx) auto const sled = ctx.view.read(keylet::account(dst)); if (!sled) return tecNO_DST; - if (((*sled)[sfFlags] & lsfRequireDestTag) && + uint32_t flags = sled->getFlags(); + + // Check if they have disallowed incoming payment channels + if (ctx.view.rules().enabled(featureDisallowIncoming) && + (flags & lsfDisallowIncomingPayChan)) + return tecNO_PERMISSION; + + if ((flags & lsfRequireDestTag) && !ctx.tx[~sfDestinationTag]) return tecDST_TAG_NEEDED; // Obeying the lsfDisallowXRP flag was a bug. Piggyback on // featureDepositAuth to remove the bug. if (!ctx.view.rules().enabled(featureDepositAuth) && - ((*sled)[sfFlags] & lsfDisallowXRP)) + (flags & lsfDisallowXRP)) return tecNO_TARGET; } diff --git a/src/ripple/app/tx/impl/SetAccount.cpp b/src/ripple/app/tx/impl/SetAccount.cpp index 85fe290ca55..b300dfcd23f 100644 --- a/src/ripple/app/tx/impl/SetAccount.cpp +++ b/src/ripple/app/tx/impl/SetAccount.cpp @@ -183,6 +183,21 @@ SetAccount::preflight(PreflightContext const& ctx) tx.isFieldPresent(sfNFTokenMinter)) return temMALFORMED; } + + if (ctx.rules.enabled(featureDisallowIncoming)) + { + if ( + ( uSetFlag == asfDisallowIncomingNFTOffer && + uClearFlag == asfDisallowIncomingNFTOffer) || + ( uSetFlag == asfDisallowIncomingCheck && + uClearFlag == asfDisallowIncomingCheck) || + ( uSetFlag == asfDisallowIncomingPayChan && + uClearFlag == asfDisallowIncomingPayChan)) + { + JLOG(j.trace()) << "Malformed transaction: Contradictory flags set."; + return temINVALID_FLAG; + } + } return preflight2(ctx); } @@ -538,6 +553,28 @@ SetAccount::doApply() sle->makeFieldAbsent(sfNFTokenMinter); } + // Set or clear flags for disallowing various incoming instruments + if (ctx_.view().rules().enabled(featureDisallowIncoming)) + { + if (uSetFlag == asfDisallowIncomingNFTOffer) + uFlagsOut |= lsfDisallowIncomingNFTOffer; + else + if (uClearFlag == asfDisallowIncomingNFTOffer) + uFlagsOut &= ~lsfDisallowIncomingNFTOffer; + + if (uSetFlag == asfDisallowIncomingCheck) + uFlagsOut |= lsfDisallowIncomingCheck; + else + if (uClearFlag == asfDisallowIncomingCheck) + uFlagsOut &= ~lsfDisallowIncomingCheck; + + if (uSetFlag == asfDisallowIncomingPayChan) + uFlagsOut |= lsfDisallowIncomingPayChan; + else + if (uClearFlag == asfDisallowIncomingPayChan) + uFlagsOut &= ~lsfDisallowIncomingPayChan; + } + if (uFlagsIn != uFlagsOut) sle->setFieldU32(sfFlags, uFlagsOut); diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index fac54c2fa71..d4e65a31af8 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -74,7 +74,7 @@ namespace detail { // Feature.cpp. Because it's only used to reserve storage, and determine how // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than // the actual number of amendments. A LogicError on startup will verify this. -static constexpr std::size_t numFeatures = 53; +static constexpr std::size_t numFeatures = 54; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -340,6 +340,7 @@ extern uint256 const featureNonFungibleTokensV1_1; extern uint256 const fixTrustLinesToSelf; extern uint256 const fixRemoveNFTokenAutoTrustLine; extern uint256 const featureImmediateOfferKilled; +extern uint256 const featureDisallowIncoming; } // namespace ripple diff --git a/src/ripple/protocol/LedgerFormats.h b/src/ripple/protocol/LedgerFormats.h index 2dd04b1264b..ffcc0be95ca 100644 --- a/src/ripple/protocol/LedgerFormats.h +++ b/src/ripple/protocol/LedgerFormats.h @@ -232,6 +232,15 @@ enum LedgerSpecificFlags { lsfDefaultRipple = 0x00800000, // True, trust lines allow rippling by default lsfDepositAuth = 0x01000000, // True, all deposits require authorization +/* // reserved for Hooks amendment + lsfTshCollect = 0x02000000, // True, allow TSH collect-calls to acc hooks +*/ + lsfDisallowIncomingNFTOffer = + 0x04000000, // True, reject new incoming NFT offers + lsfDisallowIncomingCheck = + 0x08000000, // True, reject new checks + lsfDisallowIncomingPayChan = + 0x10000000, // True, reject new paychans // ltOFFER lsfPassive = 0x00010000, diff --git a/src/ripple/protocol/TxFlags.h b/src/ripple/protocol/TxFlags.h index 0ad088c41d6..b9d256a4343 100644 --- a/src/ripple/protocol/TxFlags.h +++ b/src/ripple/protocol/TxFlags.h @@ -79,6 +79,12 @@ constexpr std::uint32_t asfGlobalFreeze = 7; constexpr std::uint32_t asfDefaultRipple = 8; constexpr std::uint32_t asfDepositAuth = 9; constexpr std::uint32_t asfAuthorizedNFTokenMinter = 10; +/* // reserved for Hooks amendment +constexpr std::uint32_t asfTshCollect = 11; +*/ +constexpr std::uint32_t asfDisallowIncomingNFTOffer = 12; +constexpr std::uint32_t asfDisallowIncomingCheck = 13; +constexpr std::uint32_t asfDisallowIncomingPayChan = 14; // OfferCreate flags: constexpr std::uint32_t tfPassive = 0x00010000; diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index fa0d167ef09..5903603f975 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -450,6 +450,7 @@ REGISTER_FEATURE(NonFungibleTokensV1_1, Supported::yes, DefaultVote::no) REGISTER_FIX (fixTrustLinesToSelf, Supported::yes, DefaultVote::no); REGISTER_FIX (fixRemoveNFTokenAutoTrustLine, Supported::yes, DefaultVote::yes); REGISTER_FEATURE(ImmediateOfferKilled, Supported::yes, DefaultVote::no); +REGISTER_FEATURE(DisallowIncoming, Supported::yes, DefaultVote::no); // The following amendments have been active for at least two years. Their // pre-amendment code has been removed and the identifiers are deprecated. diff --git a/src/test/app/Check_test.cpp b/src/test/app/Check_test.cpp index 31a2e572e70..8af86080d8b 100644 --- a/src/test/app/Check_test.cpp +++ b/src/test/app/Check_test.cpp @@ -85,6 +85,8 @@ class dest_tag class Check_test : public beast::unit_test::suite { + FeatureBitset const disallowIncoming{featureDisallowIncoming}; + static uint256 getCheckIndex(AccountID const& account, std::uint32_t uSequence) { @@ -292,6 +294,87 @@ class Check_test : public beast::unit_test::suite BEAST_EXPECT(checksOnAccount(env, alice).size() == aliceCount + 7); BEAST_EXPECT(checksOnAccount(env, bob).size() == bobCount + 7); } + + void + testCreateDisallowIncoming(FeatureBitset features) + { + // Explore many of the valid ways to create a check. + testcase("Create valid with disallow incoming"); + + using namespace test::jtx; + + Account const gw{"gateway"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + IOU const USD{gw["USD"]}; + + Env env{*this, features | disallowIncoming}; + + STAmount const startBalance{XRP(1000).value()}; + env.fund(startBalance, gw, alice, bob); + + // Note that no trust line has been set up for alice, but alice can + // still write a check for USD. You don't have to have the funds + // necessary to cover a check in order to write a check. + auto writeTwoChecksDI = [&env, &USD, this]( + Account const& from, Account const& to, TER expected) { + std::uint32_t const fromOwnerCount{ownerCount(env, from)}; + std::uint32_t const toOwnerCount{ownerCount(env, to)}; + + std::size_t const fromCkCount{checksOnAccount(env, from).size()}; + std::size_t const toCkCount{checksOnAccount(env, to).size()}; + + env(check::create(from, to, XRP(2000)), ter(expected)); + env.close(); + + env(check::create(from, to, USD(50)), ter(expected)); + env.close(); + + if (expected == tesSUCCESS) + { + BEAST_EXPECT(checksOnAccount(env, from).size() == fromCkCount + 2); + BEAST_EXPECT(checksOnAccount(env, to).size() == toCkCount + 2); + + env.require(owners(from, fromOwnerCount + 2)); + env.require( + owners(to, to == from ? fromOwnerCount + 2 : toOwnerCount)); + return; + } + + BEAST_EXPECT(checksOnAccount(env, from).size() == fromCkCount); + BEAST_EXPECT(checksOnAccount(env, to).size() == toCkCount); + + env.require(owners(from, fromOwnerCount)); + env.require( + owners(to, to == from ? fromOwnerCount : toOwnerCount)); + }; + // from to + + env(fset(bob, asfDisallowIncomingCheck)); + env(fset(alice, asfDisallowIncomingCheck)); + env.close(); + + // both alice and bob can't receive checks + writeTwoChecksDI(alice, bob, tecNO_PERMISSION); + writeTwoChecksDI(gw, alice, tecNO_PERMISSION); + + + // remove the flag from alice but not from bob + env(fclear(alice, asfDisallowIncomingCheck)); + env.close(); + + // now bob can send alice a cheque but not visa-versa + writeTwoChecksDI(bob, alice, tesSUCCESS); + writeTwoChecksDI(alice, bob, tecNO_PERMISSION); + + // remove bob's flag too + env(fclear(bob, asfDisallowIncomingCheck)); + env.close(); + + // now they can send checks freely + writeTwoChecksDI(bob, alice, tesSUCCESS); + writeTwoChecksDI(alice, bob, tesSUCCESS); + } void testCreateInvalid(FeatureBitset features) @@ -2602,6 +2685,7 @@ class Check_test : public beast::unit_test::suite { testEnabled(features); testCreateValid(features); + testCreateDisallowIncoming(features); testCreateInvalid(features); testCashXRP(features); testCashIOU(features); @@ -2621,6 +2705,7 @@ class Check_test : public beast::unit_test::suite using namespace test::jtx; auto const sa = supported_amendments(); testWithFeats(sa - featureCheckCashMakesTrustLine); + testWithFeats(sa - disallowIncoming); testWithFeats(sa); testTrustLineCreation(sa); // Test with featureCheckCashMakesTrustLine diff --git a/src/test/app/NFToken_test.cpp b/src/test/app/NFToken_test.cpp index ac86bf74988..fd0c9186808 100644 --- a/src/test/app/NFToken_test.cpp +++ b/src/test/app/NFToken_test.cpp @@ -29,6 +29,8 @@ namespace ripple { class NFToken_test : public beast::unit_test::suite { + FeatureBitset const disallowIncoming{featureDisallowIncoming}; + // Helper function that returns the owner count of an account root. static std::uint32_t ownerCount(test::jtx::Env const& env, test::jtx::Account const& acct) @@ -2963,6 +2965,124 @@ class NFToken_test : public beast::unit_test::suite } } + void + testCreateOfferDestinationDisallowIncoming(FeatureBitset features) + { + testcase("Create offer destination disallow incoming"); + + using namespace test::jtx; + + Env env{*this, features | disallowIncoming}; + + Account const issuer{"issuer"}; + Account const minter{"minter"}; + Account const buyer{"buyer"}; + Account const alice{"alice"}; + + env.fund(XRP(1000), issuer, minter, buyer, alice); + + env(token::setMinter(issuer, minter)); + env.close(); + + uint256 const nftokenID = + token::getNextID(env, issuer, 0, tfTransferable); + env(token::mint(minter, 0), + token::issuer(issuer), + txflags(tfTransferable)); + env.close(); + + // enable flag + env(fset(buyer, asfDisallowIncomingNFTOffer)); + env.close(); + + + // a sell offer from the minter to the buyer should be rejected + { + env(token::createOffer(minter, nftokenID, drops(1)), + token::destination(buyer), + txflags(tfSellNFToken), ter(tecNO_PERMISSION)); + env.close(); + BEAST_EXPECT(ownerCount(env, issuer) == 0); + BEAST_EXPECT(ownerCount(env, minter) == 1); + BEAST_EXPECT(ownerCount(env, buyer) == 0); + } + + // disable the flag + env(fclear(buyer, asfDisallowIncomingNFTOffer)); + env.close(); + + // create offer (allowed now) then cancel + { + uint256 const offerIndex = + keylet::nftoffer(minter, env.seq(minter)).key; + + env(token::createOffer(minter, nftokenID, drops(1)), + token::destination(buyer), + txflags(tfSellNFToken)); + env.close(); + + env(token::cancelOffer(minter, {offerIndex})); + env.close(); + } + + // create offer, enable flag, then cancel + { + uint256 const offerIndex = + keylet::nftoffer(minter, env.seq(minter)).key; + + env(token::createOffer(minter, nftokenID, drops(1)), + token::destination(buyer), + txflags(tfSellNFToken)); + env.close(); + + env(fset(buyer, asfDisallowIncomingNFTOffer)); + env.close(); + + env(token::cancelOffer(minter, {offerIndex})); + env.close(); + + env(fclear(buyer, asfDisallowIncomingNFTOffer)); + env.close(); + } + + + // create offer then transfer + { + uint256 const offerIndex = + keylet::nftoffer(minter, env.seq(minter)).key; + + env(token::createOffer(minter, nftokenID, drops(1)), + token::destination(buyer), + txflags(tfSellNFToken)); + env.close(); + + env(token::acceptSellOffer(buyer, offerIndex)); + env.close(); + } + + // buyer now owns the token + + // enable flag again + env(fset(buyer, asfDisallowIncomingNFTOffer)); + env.close(); + + // a random offer to buy the token + { + env(token::createOffer(alice, nftokenID, drops(1)), + token::owner(buyer), + ter(tecNO_PERMISSION)); + env.close(); + } + + // minter offer to buy the token + { + env(token::createOffer(minter, nftokenID, drops(1)), + token::owner(buyer), + ter(tecNO_PERMISSION)); + env.close(); + } + } + void testCreateOfferExpiration(FeatureBitset features) { @@ -4917,6 +5037,7 @@ class NFToken_test : public beast::unit_test::suite testMintTaxon(features); testMintURI(features); testCreateOfferDestination(features); + testCreateOfferDestinationDisallowIncoming(features); testCreateOfferExpiration(features); testCancelOffers(features); testCancelTooManyOffers(features); @@ -4937,6 +5058,7 @@ class NFToken_test : public beast::unit_test::suite FeatureBitset const fixNFTDir{fixNFTokenDirV1}; testWithFeats(all - fixNFTDir); + testWithFeats(all - disallowIncoming); testWithFeats(all); } }; diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index cf600a9fc87..4383f04dbf0 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -32,6 +32,8 @@ namespace ripple { namespace test { struct PayChan_test : public beast::unit_test::suite { + FeatureBitset const disallowIncoming{featureDisallowIncoming}; + static uint256 channel( jtx::Account const& account, @@ -175,12 +177,12 @@ struct PayChan_test : public beast::unit_test::suite } void - testSimple() + testSimple(FeatureBitset features) { testcase("simple"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto USDA = alice["USD"]; @@ -348,9 +350,75 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(bob) == preBob - feeDrops); } } + + void + testDisallowIncoming(FeatureBitset features) + { + testcase("Disallow Incoming Flag"); + using namespace jtx; + using namespace std::literals::chrono_literals; + Env env{*this, features | disallowIncoming}; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const cho = Account("cho"); + env.fund(XRP(10000), alice, bob, cho); + auto const pk = alice.pk(); + auto const settleDelay = 100s; + + // set flag on bob only + env(fset(bob, asfDisallowIncomingPayChan)); + env.close(); + + // channel creation from alice to bob is disallowed + { + auto const chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, XRP(1000), settleDelay, pk), ter(tecNO_PERMISSION)); + BEAST_EXPECT(!channelExists(*env.current(), chan)); + } + + // set flag on alice also + env(fset(alice, asfDisallowIncomingPayChan)); + env.close(); + + // channel creation from bob to alice is now disallowed + { + auto const chan = channel(bob, alice, env.seq(bob)); + env(create(bob, alice, XRP(1000), settleDelay, pk), ter(tecNO_PERMISSION)); + BEAST_EXPECT(!channelExists(*env.current(), chan)); + } + + // remove flag from bob + env(fclear(bob, asfDisallowIncomingPayChan)); + env.close(); + + // now the channel between alice and bob can exist + { + auto const chan = channel(alice, bob, env.seq(alice)); + env(create(alice, bob, XRP(1000), settleDelay, pk), ter(tesSUCCESS)); + BEAST_EXPECT(channelExists(*env.current(), chan)); + } + + // a channel from cho to alice isn't allowed + { + auto const chan = channel(cho, alice, env.seq(cho)); + env(create(cho, alice, XRP(1000), settleDelay, pk), ter(tecNO_PERMISSION)); + BEAST_EXPECT(!channelExists(*env.current(), chan)); + } + + // remove flag from alice + env(fclear(alice, asfDisallowIncomingPayChan)); + env.close(); + + // now a channel from cho to alice is allowed + { + auto const chan = channel(cho, alice, env.seq(cho)); + env(create(cho, alice, XRP(1000), settleDelay, pk), ter(tesSUCCESS)); + BEAST_EXPECT(channelExists(*env.current(), chan)); + } + } void - testCancelAfter() + testCancelAfter(FeatureBitset features) { testcase("cancel after"); using namespace jtx; @@ -360,7 +428,7 @@ struct PayChan_test : public beast::unit_test::suite auto const carol = Account("carol"); { // If dst claims after cancel after, channel closes - Env env(*this); + Env env{*this, features}; env.fund(XRP(10000), alice, bob); auto const pk = alice.pk(); auto const settleDelay = 100s; @@ -392,7 +460,7 @@ struct PayChan_test : public beast::unit_test::suite } { // Third party can close after cancel after - Env env(*this); + Env env{*this, features}; env.fund(XRP(10000), alice, bob, carol); auto const pk = alice.pk(); auto const settleDelay = 100s; @@ -415,12 +483,12 @@ struct PayChan_test : public beast::unit_test::suite } void - testExpiration() + testExpiration(FeatureBitset features) { testcase("expiration"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); @@ -481,12 +549,12 @@ struct PayChan_test : public beast::unit_test::suite } void - testSettleDelay() + testSettleDelay(FeatureBitset features) { testcase("settle delay"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(10000), alice, bob); @@ -541,12 +609,12 @@ struct PayChan_test : public beast::unit_test::suite } void - testCloseDry() + testCloseDry(FeatureBitset features) { testcase("close dry"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(10000), alice, bob); @@ -575,13 +643,13 @@ struct PayChan_test : public beast::unit_test::suite } void - testDefaultAmount() + testDefaultAmount(FeatureBitset features) { // auth amount defaults to balance if not present testcase("default amount"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(10000), alice, bob); @@ -630,7 +698,7 @@ struct PayChan_test : public beast::unit_test::suite } void - testDisallowXRP() + testDisallowXRP(FeatureBitset features) { // auth amount defaults to balance if not present testcase("Disallow XRP"); @@ -652,7 +720,7 @@ struct PayChan_test : public beast::unit_test::suite { // Create a channel where dst disallows XRP. Ignore that flag, // since it's just advisory. - Env env(*this); + Env env{*this, features}; env.fund(XRP(10000), alice, bob); env(fset(bob, asfDisallowXRP)); auto const chan = channel(alice, bob, env.seq(alice)); @@ -677,7 +745,7 @@ struct PayChan_test : public beast::unit_test::suite // Claim to a channel where dst disallows XRP (channel is // created before disallow xrp is set). Ignore that flag // since it is just advisory. - Env env(*this); + Env env{*this, features}; env.fund(XRP(10000), alice, bob); auto const chan = channel(alice, bob, env.seq(alice)); env(create(alice, bob, XRP(1000), 3600s, alice.pk())); @@ -690,14 +758,14 @@ struct PayChan_test : public beast::unit_test::suite } void - testDstTag() + testDstTag(FeatureBitset features) { // auth amount defaults to balance if not present testcase("Dst Tag"); using namespace jtx; using namespace std::literals::chrono_literals; // Create a channel where dst disallows XRP - Env env(*this); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(10000), alice, bob); @@ -720,7 +788,7 @@ struct PayChan_test : public beast::unit_test::suite } void - testDepositAuth() + testDepositAuth(FeatureBitset features) { testcase("Deposit Authorization"); using namespace jtx; @@ -731,7 +799,7 @@ struct PayChan_test : public beast::unit_test::suite auto const carol = Account("carol"); auto USDA = alice["USD"]; { - Env env(*this); + Env env{*this, features}; env.fund(XRP(10000), alice, bob, carol); env(fset(bob, asfDepositAuth)); @@ -844,13 +912,13 @@ struct PayChan_test : public beast::unit_test::suite } void - testMultiple() + testMultiple(FeatureBitset features) { // auth amount defaults to balance if not present testcase("Multiple channels to the same account"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); env.fund(XRP(10000), alice, bob); @@ -867,13 +935,13 @@ struct PayChan_test : public beast::unit_test::suite } void - testAccountChannelsRPC() + testAccountChannelsRPC(FeatureBitset features) { testcase("AccountChannels RPC"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const charlie = Account("charlie", KeyType::ed25519); @@ -922,7 +990,7 @@ struct PayChan_test : public beast::unit_test::suite } void - testAccountChannelsRPCMarkers() + testAccountChannelsRPCMarkers(FeatureBitset features) { testcase("Account channels RPC markers"); @@ -941,7 +1009,7 @@ struct PayChan_test : public beast::unit_test::suite return r; }(); - Env env(*this); + Env env{*this, features}; env.fund(XRP(10000), alice); for (auto const& a : bobs) { @@ -1038,7 +1106,7 @@ struct PayChan_test : public beast::unit_test::suite } void - testAccountChannelsRPCSenderOnly() + testAccountChannelsRPCSenderOnly(FeatureBitset features) { // Check that the account_channels command only returns channels owned // by the account @@ -1049,7 +1117,7 @@ struct PayChan_test : public beast::unit_test::suite auto const alice = Account("alice"); auto const bob = Account("bob"); - Env env(*this); + Env env{*this, features}; env.fund(XRP(10000), alice, bob); // Create a channel from alice to bob and from bob to alice @@ -1075,12 +1143,12 @@ struct PayChan_test : public beast::unit_test::suite } void - testAuthVerifyRPC() + testAuthVerifyRPC(FeatureBitset features) { testcase("PayChan Auth/Verify RPC"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const charlie = Account("charlie", KeyType::ed25519); @@ -1415,12 +1483,12 @@ struct PayChan_test : public beast::unit_test::suite } void - testOptionalFields() + testOptionalFields(FeatureBitset features) { testcase("Optional Fields"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto const carol = Account("carol"); @@ -1466,12 +1534,12 @@ struct PayChan_test : public beast::unit_test::suite } void - testMalformedPK() + testMalformedPK(FeatureBitset features) { testcase("malformed pk"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto USDA = alice["USD"]; @@ -1536,7 +1604,7 @@ struct PayChan_test : public beast::unit_test::suite } void - testMetaAndOwnership() + testMetaAndOwnership(FeatureBitset features) { testcase("Metadata & Ownership"); @@ -1587,7 +1655,7 @@ struct PayChan_test : public beast::unit_test::suite { // Test with adding the paychan to the recipient's owner directory - Env env(*this); + Env env{*this, features}; env.fund(XRP(10000), alice, bob); env(create(alice, bob, XRP(1000), settleDelay, pk)); env.close(); @@ -1644,7 +1712,7 @@ struct PayChan_test : public beast::unit_test::suite } void - testAccountDelete() + testAccountDelete(FeatureBitset features) { testcase("Account Delete"); using namespace test::jtx; @@ -1878,12 +1946,12 @@ struct PayChan_test : public beast::unit_test::suite } void - testUsingTickets() + testUsingTickets(FeatureBitset features) { testcase("using tickets"); using namespace jtx; using namespace std::literals::chrono_literals; - Env env(*this); + Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); auto USDA = alice["USD"]; @@ -2039,28 +2107,39 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(env.seq(bob) == bobSeq); } + void + testWithFeats(FeatureBitset features) + { + testSimple(features); + testDisallowIncoming(features); + testCancelAfter(features); + testSettleDelay(features); + testExpiration(features); + testCloseDry(features); + testDefaultAmount(features); + testDisallowXRP(features); + testDstTag(features); + testDepositAuth(features); + testMultiple(features); + testAccountChannelsRPC(features); + testAccountChannelsRPCMarkers(features); + testAccountChannelsRPCSenderOnly(features); + testAuthVerifyRPC(features); + testOptionalFields(features); + testMalformedPK(features); + testMetaAndOwnership(features); + testAccountDelete(features); + testUsingTickets(features); + } + +public: void run() override { - testSimple(); - testCancelAfter(); - testSettleDelay(); - testExpiration(); - testCloseDry(); - testDefaultAmount(); - testDisallowXRP(); - testDstTag(); - testDepositAuth(); - testMultiple(); - testAccountChannelsRPC(); - testAccountChannelsRPCMarkers(); - testAccountChannelsRPCSenderOnly(); - testAuthVerifyRPC(); - testOptionalFields(); - testMalformedPK(); - testMetaAndOwnership(); - testAccountDelete(); - testUsingTickets(); + using namespace test::jtx; + FeatureBitset const all{supported_amendments()}; + testWithFeats(all - disallowIncoming); + testWithFeats(all); } };