Fully Support NFTokenMint/Burn

This commit is contained in:
tequ
2025-09-20 17:03:20 +09:00
parent fed56b2eeb
commit d4947386d7
5 changed files with 147 additions and 53 deletions

View File

@@ -1644,26 +1644,75 @@ public:
void
testNFToken()
{
// testcase("NFToken");
// 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");
testcase("NFToken");
using namespace test::jtx;
Account const alice("alice");
Account const bob("bob");
Account const sponsor("sponsor");
Account const sponsor2("sponsor2");
// env.fund(XRP(1000000), alice, bob, sponsor);
// env.close();
{
Env env{*this, testable_amendments()};
// // NFTokenMint
// env(token::mint(alice),
// sponsor::as(sponsor, tfSponsorReserve),
// sponsor::sig(sponsor));
// env.close();
env.fund(XRP(1000000), alice, bob, sponsor);
env.close();
// BEAST_EXPECT(ownerCount(env, alice) == 1);
// BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
// BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// NFTokenMint
uint256 const nftId{token::getNextID(env, alice, 0)};
env(token::mint(alice),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
// 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);
}
{
// multiple nft page process
Env env{*this, testable_amendments()};
env.fund(XRP(1000000), alice, bob, sponsor);
env.close();
auto const nftCount = 200;
// NFTokenMint
for (auto i = 0; i < nftCount; i++)
{
env(token::mint(alice),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
}
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
@@ -1694,7 +1743,7 @@ public:
BEAST_EXPECT(ownerCount(env, alice) == 1);
// NFTokenOfferCreate
uint256 const offerIndex =
uint256 const offerIndex1 =
keylet::nftoffer(alice, env.seq(alice)).key;
env(token::createOffer(alice, nftId, XRP(1)),
token::destination(bob),
@@ -1707,19 +1756,32 @@ public:
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
uint256 const offerIndex2 =
keylet::nftoffer(alice, env.seq(alice)).key;
env(token::createOffer(alice, nftId, XRP(1)),
token::destination(bob),
txflags(tfSellNFToken),
sponsor::as(sponsor, tfSponsorReserve),
sponsor::sig(sponsor));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 3);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 2);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2);
// transfer sponsor
env(sponsor::transfer(alice, offerIndex),
env(sponsor::transfer(alice, offerIndex1),
sponsor::as(sponsor2, tfSponsorReserve),
sponsor::sig(sponsor2));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 2);
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
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, {offerIndex}));
env(token::cancelOffer(alice, {offerIndex1, offerIndex2}));
env.close();
BEAST_EXPECT(ownerCount(env, alice) == 1);
@@ -2546,7 +2608,7 @@ public:
testDID();
testEscrow();
testMPToken();
// testNFToken();
testNFToken();
testNFTokenOffer();
testPayChan();
testPermissionedDomain();

View File

@@ -445,8 +445,9 @@ NFTokenAcceptOffer::transferNFToken(
std::uint32_t const buyerOwnerCountBefore =
sleBuyer->getFieldU32(sfOwnerCount);
auto const insertRet =
nft::insertToken(view(), buyer, std::move(tokenAndPage->token));
auto const sponsor = getTxReserveSponsorAccountID(ctx_.tx);
auto const insertRet = nft::insertToken(
view(), buyer, sponsor, std::move(tokenAndPage->token));
// if fixNFTokenReserve is enabled, check if the buyer has sufficient
// reserve to own a new object, if their OwnerCount changed.

View File

@@ -319,8 +319,9 @@ NFTokenMint::doApply()
object.setFieldVL(sfURI, *uri);
});
if (TER const ret =
nft::insertToken(ctx_.view(), account_, std::move(newToken));
auto const sponsor = getTxReserveSponsorAccountID(ctx_.tx);
if (TER const ret = nft::insertToken(
ctx_.view(), account_, sponsor, std::move(newToken));
ret != tesSUCCESS)
return ret;

View File

@@ -66,8 +66,13 @@ static std::shared_ptr<SLE>
getPageForToken(
ApplyView& view,
AccountID const& owner,
std::optional<AccountID> const& sponsor,
uint256 const& id,
std::function<void(ApplyView&, AccountID const&)> const& createCallback)
std::function<void(
ApplyView&,
std::shared_ptr<SLE> const&,
AccountID const&,
std::optional<AccountID> const&)> const& createCallback)
{
auto const base = keylet::nftpage_min(owner);
auto const first = keylet::nftpage(base, id);
@@ -87,7 +92,7 @@ getPageForToken(
cp = std::make_shared<SLE>(last);
cp->setFieldArray(sfNFTokens, arr);
view.insert(cp);
createCallback(view, owner);
createCallback(view, cp, owner, sponsor);
return cp;
}
@@ -215,7 +220,7 @@ getPageForToken(
cp->setFieldH256(sfPreviousPageMin, np->key());
view.update(cp);
createCallback(view, owner);
createCallback(view, np, owner, sponsor);
// fixNFTokenDirV1 corrects a bug in the initial implementation that
// would put an NFT in the wrong page. The problem was caused by an
@@ -277,7 +282,11 @@ changeTokenURI(
/** Insert the token in the owner's token directory. */
TER
insertToken(ApplyView& view, AccountID owner, STObject&& nft)
insertToken(
ApplyView& view,
AccountID owner,
std::optional<AccountID> const& sponsor,
STObject&& nft)
{
XRPL_ASSERT(
nft.isFieldPresent(sfNFTokenID),
@@ -289,14 +298,22 @@ insertToken(ApplyView& view, AccountID owner, STObject&& nft)
std::shared_ptr<SLE> page = getPageForToken(
view,
owner,
sponsor,
nft[sfNFTokenID],
[](ApplyView& view, AccountID const& owner) {
[](ApplyView& view,
std::shared_ptr<SLE> const& newPage,
AccountID const& owner,
std::optional<AccountID> const& sponsor) {
std::optional<std::shared_ptr<SLE>> const sponsorSle = sponsor
? view.peek(keylet::account(*sponsor))
: std::optional<std::shared_ptr<SLE>>{std::nullopt};
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
std::nullopt,
sponsorSle,
1,
beast::Journal{beast::Journal::getNullSink()});
addSponsorToLedgerEntry(newPage, sponsorSle);
});
if (!page)
@@ -450,22 +467,25 @@ removeToken(
curr->setFieldArray(sfNFTokens, arr);
view.update(curr);
int cnt = 0;
if (prev && mergePages(view, prev, curr))
cnt--;
{
auto const sponsor = getLedgerEntryReserveSponsor(view, prev);
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
sponsor,
-1,
beast::Journal{beast::Journal::getNullSink()});
}
if (next && mergePages(view, curr, next))
cnt--;
if (cnt != 0)
{
auto const sponsor = getLedgerEntryReserveSponsor(view, curr);
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
sponsor,
cnt,
-1,
beast::Journal{beast::Journal::getNullSink()});
}
@@ -501,7 +521,7 @@ removeToken(
curr->makeFieldAbsent(sfPreviousPageMin);
}
auto const sponsor = getLedgerEntryReserveSponsor(view, curr);
auto const sponsor = getLedgerEntryReserveSponsor(view, prev);
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
@@ -535,9 +555,15 @@ removeToken(
view.update(next);
}
view.erase(curr);
auto const sponsor = getLedgerEntryReserveSponsor(view, curr);
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
getLedgerEntryReserveSponsor(view, curr),
-1,
beast::Journal{beast::Journal::getNullSink()});
int cnt = 1;
view.erase(curr);
// Since we're here, try to consolidate the previous and current pages
// of the page we removed (if any) into one. mergePages() _should_
@@ -552,14 +578,14 @@ removeToken(
view,
view.peek(Keylet(ltNFTOKEN_PAGE, prev->key())),
view.peek(Keylet(ltNFTOKEN_PAGE, next->key()))))
cnt++;
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
std::nullopt,
-1 * cnt,
beast::Journal{beast::Journal::getNullSink()});
{
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
getLedgerEntryReserveSponsor(view, prev),
-1,
beast::Journal{beast::Journal::getNullSink()});
}
return tesSUCCESS;
}

View File

@@ -70,7 +70,11 @@ findTokenAndPage(
/** Insert the token in the owner's token directory. */
TER
insertToken(ApplyView& view, AccountID owner, STObject&& nft);
insertToken(
ApplyView& view,
AccountID owner,
std::optional<AccountID> const& sponsor,
STObject&& nft);
/** Remove the token from the owner's token directory. */
TER