From 9a7d1a114c5c09d95afc33eb10ae3e08e621fce9 Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Thu, 25 Jun 2026 12:30:04 -0400 Subject: [PATCH] Cut NFT out of scope (#7616) --- include/xrpl/ledger/helpers/NFTokenHelpers.h | 3 +- src/libxrpl/ledger/helpers/NFTokenHelpers.cpp | 92 ++--- .../tx/transactors/nft/NFTokenAcceptOffer.cpp | 17 +- .../tx/transactors/nft/NFTokenCreateOffer.cpp | 1 - .../tx/transactors/nft/NFTokenMint.cpp | 29 +- src/test/app/Sponsor_test.cpp | 354 ------------------ 6 files changed, 46 insertions(+), 450 deletions(-) diff --git a/include/xrpl/ledger/helpers/NFTokenHelpers.h b/include/xrpl/ledger/helpers/NFTokenHelpers.h index 86036fd023..362cfe5a8c 100644 --- a/include/xrpl/ledger/helpers/NFTokenHelpers.h +++ b/include/xrpl/ledger/helpers/NFTokenHelpers.h @@ -39,7 +39,7 @@ findTokenAndPage(ApplyView& view, AccountID const& owner, uint256 const& nftoken /** Insert the token in the owner's token directory. */ TER -insertToken(ApplyView& view, STTx const& tx, AccountID owner, SLE::ref sponsorSle, STObject&& nft); +insertToken(ApplyView& view, AccountID owner, STObject&& nft); /** Remove the token from the owner's token directory. */ TER @@ -107,7 +107,6 @@ tokenOfferCreatePreclaim( TER tokenOfferCreateApply( ApplyView& view, - STTx const& tx, AccountID const& acctID, STAmount const& amount, std::optional const& dest, diff --git a/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp index 88afdeb264..93dbae0467 100644 --- a/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp @@ -4,14 +4,12 @@ #include #include #include -#include #include #include #include #include #include #include -#include #include #include #include @@ -23,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -35,7 +32,7 @@ #include #include #include -#include +#include #include #include #include @@ -70,13 +67,12 @@ locatePage(ApplyView& view, AccountID const& owner, uint256 const& id) Keylet(ltNFTOKEN_PAGE, view.succ(first.key, last.key.next()).value_or(last.key))); } -static std::expected +static SLE::pointer getPageForToken( ApplyView& view, - STTx const& tx, AccountID const& owner, - SLE::ref sponsorSle, - uint256 const& id) + uint256 const& id, + std::function const& createCallback) { auto const base = keylet::nftpageMin(owner); auto const first = keylet::nftpage(base, id); @@ -88,21 +84,6 @@ getPageForToken( auto cp = view.peek(Keylet(ltNFTOKEN_PAGE, view.succ(first.key, last.key.next()).value_or(last.key))); - auto const onNewPage = [&](SLE::ref newPage) -> TER { - if (isReserveSponsored(tx)) - { - auto const ownerSle = view.read(keylet::account(owner)); - auto const ownerBalance = ownerSle->getFieldAmount(sfBalance); - if (auto const ret = - checkInsufficientReserve(view, tx, ownerSle, ownerBalance, sponsorSle, 1); - !isTesSuccess(ret)) - return ret; - } - adjustOwnerCount(view, view.peek(keylet::account(owner)), sponsorSle, 1); - addSponsorToLedgerEntry(newPage, sponsorSle); - return tesSUCCESS; - }; - // A suitable page doesn't exist; we'll have to create one. if (!cp) { @@ -110,9 +91,7 @@ getPageForToken( cp = std::make_shared(last); cp->setFieldArray(sfNFTokens, arr); view.insert(cp); - - if (auto const ret = onNewPage(cp); !isTesSuccess(ret)) - return std::unexpected(ret); + createCallback(view, owner); return cp; } @@ -225,8 +204,7 @@ getPageForToken( cp->setFieldH256(sfPreviousPageMin, np->key()); view.update(cp); - if (auto const ret = onNewPage(np); !isTesSuccess(ret)) - return std::unexpected(ret); + createCallback(view, owner); return (first.key < np->key()) ? np : cp; } @@ -282,33 +260,33 @@ changeTokenURI( /** Insert the token in the owner's token directory. */ TER -insertToken(ApplyView& view, STTx const& tx, AccountID owner, SLE::ref sponsorSle, STObject&& nft) +insertToken(ApplyView& view, AccountID owner, STObject&& nft) { XRPL_ASSERT(nft.isFieldPresent(sfNFTokenID), "xrpl::nft::insertToken : has NFT token"); // First, we need to locate the page the NFT belongs to, creating it // if necessary. This operation may fail if it is impossible to insert // the NFT. - auto const page = getPageForToken(view, tx, owner, sponsorSle, nft[sfNFTokenID]); + SLE::pointer const page = + getPageForToken(view, owner, nft[sfNFTokenID], [](ApplyView& view, AccountID const& owner) { + adjustOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()}); + }); - if (!page.has_value()) - return page.error(); - - if (!(*page)) + if (!page) return tecNO_SUITABLE_NFTOKEN_PAGE; { - auto arr = (*page)->getFieldArray(sfNFTokens); + auto arr = page->getFieldArray(sfNFTokens); arr.pushBack(std::move(nft)); arr.sort([](STObject const& o1, STObject const& o2) { return compareTokens(o1.getFieldH256(sfNFTokenID), o2.getFieldH256(sfNFTokenID)); }); - (*page)->setFieldArray(sfNFTokens, arr); + page->setFieldArray(sfNFTokens, arr); } - view.update((*page)); + view.update(page); return tesSUCCESS; } @@ -429,11 +407,18 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S curr->setFieldArray(sfNFTokens, arr); view.update(curr); + int cnt = 0; + if (prev && mergePages(view, prev, curr)) - adjustOwnerCountObj(view, owner, prev, -1); + cnt--; if (next && mergePages(view, curr, next)) - adjustOwnerCountObj(view, owner, curr, -1); + cnt--; + + if (cnt != 0) + { + adjustOwnerCount(view, owner, {}, cnt, beast::Journal{beast::Journal::getNullSink()}); + } return tesSUCCESS; } @@ -467,7 +452,7 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S curr->makeFieldAbsent(sfPreviousPageMin); } - adjustOwnerCountObj(view, owner, prev, -1); + adjustOwnerCount(view, owner, {}, -1, beast::Journal{beast::Journal::getNullSink()}); view.update(curr); view.erase(prev); @@ -503,10 +488,10 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S view.update(next); } - adjustOwnerCountObj(view, owner, curr, -1); - view.erase(curr); + int cnt = 1; + // Since we're here, try to consolidate the previous and current pages // of the page we removed (if any) into one. mergePages() _should_ // always return false. Since tokens are burned one at a time, there @@ -520,9 +505,9 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S view, view.peek(Keylet(ltNFTOKEN_PAGE, prev->key())), view.peek(Keylet(ltNFTOKEN_PAGE, next->key())))) - { - adjustOwnerCountObj(view, owner, prev, -1); - } + cnt++; + + adjustOwnerCount(view, owner, {}, -1 * cnt, beast::Journal{beast::Journal::getNullSink()}); return tesSUCCESS; } @@ -638,7 +623,7 @@ deleteTokenOffer(ApplyView& view, SLE::ref offer) false)) return false; - adjustOwnerCountObj(view, owner, offer, -1); + adjustOwnerCount(view, owner, {}, -1, beast::Journal{beast::Journal::getNullSink()}); view.erase(offer); return true; @@ -917,7 +902,6 @@ tokenOfferCreatePreclaim( TER tokenOfferCreateApply( ApplyView& view, - STTx const& tx, AccountID const& acctID, STAmount const& amount, std::optional const& dest, @@ -929,14 +913,8 @@ tokenOfferCreateApply( std::uint32_t txFlags) { Keylet const acctKeylet = keylet::account(acctID); - auto const acct = view.read(acctKeylet); - auto const sponsorSle = getTxReserveSponsor(view, tx); - if (!sponsorSle) - return sponsorSle.error(); // LCOV_EXCL_LINE - if (auto const ret = - checkInsufficientReserve(view, tx, acct, priorBalance, *sponsorSle, 1, 0, j); - !isTesSuccess(ret)) - return ret; + if (auto const acct = view.read(acctKeylet); priorBalance < accountReserve(view, acct, j, 1)) + return tecINSUFFICIENT_RESERVE; auto const offerID = keylet::nftoffer(acctID, seqProxy.value()); @@ -983,13 +961,11 @@ tokenOfferCreateApply( if (dest) (*offer)[sfDestination] = *dest; - addSponsorToLedgerEntry(offer, *sponsorSle); - view.insert(offer); } // Update owner count. - adjustOwnerCount(view, view.peek(acctKeylet), *sponsorSle, 1, j); + adjustOwnerCount(view, acctID, {}, 1, j); return tesSUCCESS; } diff --git a/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp index 2298e2a8a6..f37a3ccfa0 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -374,12 +373,7 @@ NFTokenAcceptOffer::transferNFToken( std::uint32_t const buyerOwnerCountBefore = sleBuyer->getFieldU32(sfOwnerCount); - auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx); - if (!sponsorSle) - return sponsorSle.error(); // LCOV_EXCL_LINE - - auto const insertRet = - nft::insertToken(view(), ctx_.tx, buyer, *sponsorSle, std::move(tokenAndPage->token)); + auto const insertRet = nft::insertToken(view(), buyer, std::move(tokenAndPage->token)); // if fixNFTokenReserve is enabled, check if the buyer has sufficient // reserve to own a new object, if their OwnerCount changed. @@ -399,13 +393,8 @@ NFTokenAcceptOffer::transferNFToken( auto const buyerOwnerCountAfter = sleBuyer->getFieldU32(sfOwnerCount); if (buyerOwnerCountAfter > buyerOwnerCountBefore) { - SLE::const_pointer buyerSponsorSle; - if (accountID_ == buyer) - buyerSponsorSle = *sponsorSle; - if (auto const ret = checkInsufficientReserve( - ctx_.view(), ctx_.tx, sleBuyer, buyerBalance, buyerSponsorSle, 0, 0, j_); - !isTesSuccess(ret)) - return ret; + if (buyerBalance < accountReserve(view(), sleBuyer, j_)) + return tecINSUFFICIENT_RESERVE; } } diff --git a/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp index 11a2f38872..1948f3803d 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp @@ -77,7 +77,6 @@ NFTokenCreateOffer::doApply() // Use implementation shared with NFTokenMint return nft::tokenOfferCreateApply( view(), - ctx_.tx, ctx_.tx[sfAccount], ctx_.tx[sfAmount], ctx_.tx[~sfDestination], diff --git a/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp index 4270f50c59..9a158aafca 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -306,12 +305,7 @@ NFTokenMint::doApply() object.setFieldVL(sfURI, *uri); }); - auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx); - if (!sponsorSle) - return sponsorSle.error(); // LCOV_EXCL_LINE - - if (TER const ret = - nft::insertToken(ctx_.view(), ctx_.tx, accountID_, *sponsorSle, std::move(newToken)); + if (TER const ret = nft::insertToken(ctx_.view(), accountID_, std::move(newToken)); !isTesSuccess(ret)) return ret; @@ -322,7 +316,6 @@ NFTokenMint::doApply() // because a Mint is only allowed to create a sell offer. if (TER const ter = nft::tokenOfferCreateApply( view(), - ctx_.tx, ctx_.tx[sfAccount], ctx_.tx[sfAmount], ctx_.tx[~sfDestination], @@ -339,21 +332,15 @@ NFTokenMint::doApply() // allows NFTs to be added to the page (and burn fees) without // requiring the reserve to be met each time. The reserve is // only managed when a new NFT page or sell offer is added. - if (auto const ownerCountAfter = - view().read(keylet::account(accountID_))->getFieldU32(sfOwnerCount); + auto const sleAccount = view().read(keylet::account(accountID_)); + if (!sleAccount) + return tecINTERNAL; // LCOV_EXCL_LINE + + if (auto const ownerCountAfter = sleAccount->getFieldU32(sfOwnerCount); ownerCountAfter > ownerCountBefore) { - if (auto const ret = checkInsufficientReserve( - ctx_.view(), - ctx_.tx, - view().read(keylet::account(accountID_)), - preFeeBalance_, - *sponsorSle, - 0, - 0, - j_); - !isTesSuccess(ret)) - return ret; + if (preFeeBalance_ < accountReserve(view(), sleAccount, j_)) + return tecINSUFFICIENT_RESERVE; } return tesSUCCESS; } diff --git a/src/test/app/Sponsor_test.cpp b/src/test/app/Sponsor_test.cpp index 59a9b4f6ef..ae836c5d4d 100644 --- a/src/test/app/Sponsor_test.cpp +++ b/src/test/app/Sponsor_test.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -3733,357 +3732,6 @@ public: } } - void - testNFToken(bool cosigning) - { - testcase("NFToken"); - using namespace test::jtx; - Account const alice("alice"); - Account const bob("bob"); - Account const sponsor("sponsor"); - Account const sponsor2("sponsor2"); - - { - Env env{*this, testableAmendments()}; - - env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); - env.close(); - - // NFTokenMint - uint256 nftId; - testEachSponsorship( - env, - cosigning, - sponsor, - alice, - 1, - 1, - tecINSUFFICIENT_RESERVE, - [&](Env& env, auto const& submit) { - nftId = token::getNextID(env, alice, 0); - submit(token::mint(alice)); - }); - - // transfer sponsor - auto const keylet = keylet::nftpageMax(alice); - if (cosigning) - { - env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key), - sponsor::As(sponsor2, spfSponsorReserve), - Sig(sfSponsorSignature, sponsor2)); - env.close(); - } - else - { - env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); - env.close(); - - env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key), - sponsor::As(sponsor2, spfSponsorReserve)); - } - // NFTokenBurn - env(token::burn(alice, nftId)); - env.close(); - - BEAST_EXPECT(ownerCount(env, alice) == 0); - BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); - BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); - BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); - - // NFTokenMintOffer - testEachSponsorship( - env, - cosigning, - sponsor, - alice, - 2, - 2, - tecINSUFFICIENT_RESERVE, - [&](Env& env, auto const& submit) { - submit(token::mint(alice), token::Amount(XRP(100))); - }); - } - - { - // multiple nft page process - Env env{*this, testableAmendments()}; - - env.fund(XRP(1000000), alice, bob, sponsor); - env.close(); - - auto const nftCount = 200; - - // NFTokenMint - if (cosigning) - { - for (auto i = 0; i < nftCount; i++) - { - env(token::mint(alice), - sponsor::As(sponsor, spfSponsorReserve), - Sig(sfSponsorSignature, sponsor)); - } - } - else - { - env(sponsor::set_reserve(sponsor, 0, 8), sponsor::SponseeAcc(alice)); - env.close(); - for (auto i = 0; i < nftCount; i++) - { - env(token::mint(alice), sponsor::As(sponsor, spfSponsorReserve)); - } - } - env.close(); - - BEAST_EXPECT(ownerCount(env, alice) == sponsoredOwnerCount(env, alice)); - BEAST_EXPECT(sponsoredOwnerCount(env, alice) == sponsoringOwnerCount(env, sponsor)); - - // NFTokenBurn - for (auto i = 0; i < nftCount; i++) - { - auto const nftId = token::getID(env, alice, 0, i, 0, 0); - env(token::burn(alice, nftId)); - } - env.close(); - - BEAST_EXPECT(ownerCount(env, alice) == 0); - BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); - BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); - } - } - - void - testNFTokenOffer(bool cosigning) - { - testcase("NFTokenOffer"); - using namespace test::jtx; - Account const alice("alice"); - Account const bob("bob"); - Account const broker("broker"); - Account const sponsor("sponsor"); - Account const sponsor2("sponsor2"); - - auto const taxon = 0u; - - { - // Mint + CreateOffer + CancelOffer - Env env{*this, testableAmendments()}; - env.fund(XRP(1000000), alice, bob, sponsor, sponsor2); - env.close(); - - // Mint - uint256 const nftId{token::getNextID(env, alice, taxon, tfTransferable)}; - env(token::mint(alice, taxon), Txflags(tfTransferable)); - env.close(); - - // NFTokenOfferCreate - uint256 offerIndex1; - testEachSponsorship( - env, - cosigning, - sponsor, - alice, - 1, - 1, - tecINSUFFICIENT_RESERVE, - [&](Env& env, auto const& submit) { - offerIndex1 = keylet::nftoffer(alice, env.seq(alice)).key; - submit( - token::createOffer(alice, nftId, XRP(1)), - token::Destination(bob), - Txflags(tfSellNFToken)); - }); - - uint256 offerIndex2; - testEachSponsorship( - env, - cosigning, - sponsor, - alice, - 1, - 1, - tecINSUFFICIENT_RESERVE, - [&](Env& env, auto const& submit) { - offerIndex2 = keylet::nftoffer(alice, env.seq(alice)).key; - submit( - token::createOffer(alice, nftId, XRP(1)), - token::Destination(bob), - Txflags(tfSellNFToken)); - }); - - // transfer sponsor - if (cosigning) - { - env(sponsor::transfer(alice, tfSponsorshipReassign, offerIndex1), - sponsor::As(sponsor2, spfSponsorReserve), - Sig(sfSponsorSignature, sponsor2)); - env.close(); - } - else - { - env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice)); - env.close(); - - env(sponsor::transfer(alice, tfSponsorshipReassign, offerIndex1), - sponsor::As(sponsor2, spfSponsorReserve)); - env.close(); - } - - BEAST_EXPECT(ownerCount(env, alice) == 3); - BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2); - BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1); - BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1); - - // NFTokenOfferCancel - env(token::cancelOffer(alice, {offerIndex1, offerIndex2})); - env.close(); - - BEAST_EXPECT(ownerCount(env, alice) == 1); - BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); - BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); - BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); - } - - { - // Mint + CreateSellOffer + AcceptSellOffer - Env env{*this, testableAmendments()}; - env.fund(XRP(1000000), alice, bob, sponsor); - env.close(); - - // Mint - uint256 const nftId{token::getNextID(env, alice, taxon, tfTransferable)}; - env(token::mint(alice, taxon), Txflags(tfTransferable)); - env.close(); - - // NFTokenOfferCreate - uint256 offerIndex; - testEachSponsorship( - env, - cosigning, - sponsor, - alice, - 1, - 1, - tecINSUFFICIENT_RESERVE, - [&](Env& env, auto const& submit) { - offerIndex = keylet::nftoffer(alice, env.seq(alice)).key; - submit( - token::createOffer(alice, nftId, XRP(1)), - token::Destination(bob), - Txflags(tfSellNFToken)); - }); - - // NFTokenOfferAccept - env(token::acceptSellOffer(bob, offerIndex)); - env.close(); - - BEAST_EXPECT(ownerCount(env, alice) == 0); - BEAST_EXPECT(ownerCount(env, bob) == 1); - BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); - BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); - BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); - } - - { - // Mint + CreateBuyOffer + AcceptBuyOffer - Env env{*this, testableAmendments()}; - env.fund(XRP(1000000), alice, bob, sponsor); - env.close(); - - // Mint - uint256 const nftId{token::getNextID(env, alice, taxon, tfTransferable)}; - env(token::mint(alice, taxon), Txflags(tfTransferable)); - env.close(); - - // NFTokenOfferCreate - uint256 offerIndex; - testEachSponsorship( - env, - cosigning, - sponsor, - bob, - 1, - 1, - tecINSUFFICIENT_RESERVE, - [&](Env& env, auto const& submit) { - offerIndex = keylet::nftoffer(bob, env.seq(bob)).key; - submit( - token::createOffer(bob, nftId, XRP(1)), - token::Owner(alice), - token::Destination(alice)); - }); - - // NFTokenOfferAccept - env(token::acceptBuyOffer(alice, offerIndex)); - env.close(); - - BEAST_EXPECT(ownerCount(env, alice) == 0); - BEAST_EXPECT(ownerCount(env, bob) == 1); - BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); - BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); - BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); - } - { - // Broker - Env env{*this, testableAmendments()}; - env.fund(XRP(1000000), alice, bob, broker, sponsor, sponsor2); - env.close(); - - // Mint - uint256 const nftId{token::getNextID(env, alice, taxon, tfTransferable)}; - env(token::mint(alice, taxon), Txflags(tfTransferable)); - env.close(); - - BEAST_EXPECT(ownerCount(env, alice) == 1); - - // NFTokenOfferCreate (BuyOffer) - uint256 buyOfferIndex; - testEachSponsorship( - env, - cosigning, - sponsor, - bob, - 1, - 1, - tecINSUFFICIENT_RESERVE, - [&](Env& env, auto const& submit) { - buyOfferIndex = keylet::nftoffer(bob, env.seq(bob)).key; - submit( - token::createOffer(bob, nftId, XRP(1)), - token::Owner(alice), - token::Destination(broker)); - }); - - // NFTokenOfferCreate (SellOffer) - uint256 sellOfferIndex; - testEachSponsorship( - env, - cosigning, - sponsor2, - alice, - 1, - 1, - tecINSUFFICIENT_RESERVE, - [&](Env& env, auto const& submit) { - sellOfferIndex = keylet::nftoffer(alice, env.seq(alice)).key; - submit( - token::createOffer(alice, nftId, XRP(1)), - Txflags(tfSellNFToken), - token::Destination(broker)); - }); - - // NFTokenOfferAccept - env(token::brokerOffers(broker, buyOfferIndex, sellOfferIndex)); - env.close(); - - BEAST_EXPECT(ownerCount(env, alice) == 0); - BEAST_EXPECT(ownerCount(env, bob) == 1); - BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0); - BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); - BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); - BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0); - } - } - void testPayChan(bool cosigning) { @@ -6062,8 +5710,6 @@ public: testDID(cosigning); testEscrow(cosigning); testMPToken(cosigning); - testNFToken(cosigning); - testNFTokenOffer(cosigning); testPayChan(cosigning); testPermissionedDomain(cosigning); testOracle(cosigning);