mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-21 22:30:57 +00:00
Cut NFT out of scope (#7616)
This commit is contained in:
@@ -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<AccountID> const& dest,
|
||||
|
||||
@@ -4,14 +4,12 @@
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -23,7 +21,6 @@
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
@@ -35,7 +32,7 @@
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -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<SLE::pointer, TER>
|
||||
static SLE::pointer
|
||||
getPageForToken(
|
||||
ApplyView& view,
|
||||
STTx const& tx,
|
||||
AccountID const& owner,
|
||||
SLE::ref sponsorSle,
|
||||
uint256 const& id)
|
||||
uint256 const& id,
|
||||
std::function<void(ApplyView&, AccountID const&)> 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<SLE>(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<AccountID> 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;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
#include <test/jtx/sponsor.h>
|
||||
#include <test/jtx/ter.h>
|
||||
#include <test/jtx/ticket.h>
|
||||
#include <test/jtx/token.h>
|
||||
#include <test/jtx/trust.h>
|
||||
#include <test/jtx/txflags.h>
|
||||
#include <test/jtx/vault.h>
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user