mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-04 17:27:00 +00:00
Fully Support NFTokenMint/Burn
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user