Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XLS-52d: NFTokenMintOffer #4845

Merged
merged 24 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
df092fc
NFTokenMIntOffer initial
tequdev Nov 19, 2023
351c135
clang-format
tequdev Nov 19, 2023
dc525c1
Merge commit '06251aa76f5cff9a546107432d959e09a7a93f29' into featureN…
tequdev Dec 1, 2023
650af48
clang-format
tequdev Dec 1, 2023
c517ae9
Merge branch 'develop' into featureNFTokenMintOffer
tequdev Dec 5, 2023
5815a57
Update Feature.h
tequdev Dec 5, 2023
f9e4cda
Update NFToken_test.cpp
tequdev Dec 5, 2023
a7f374e
fix sfAmount field
tequdev Dec 5, 2023
22aaf09
Merge branch 'develop' into featureNFTokenMintOffer
tequdev Dec 13, 2023
2321b8c
Merge commit '901152bd930447720ca4e738a10ffd878f401a53' into featureN…
tequdev Jan 25, 2024
b8954f4
fix expiration, reserve, offer_id meta
tequdev Jan 31, 2024
e48768d
clang-format
tequdev Jan 31, 2024
b89fdd6
Merge branch 'develop' into featureNFTokenMintOffer
tequdev Jan 31, 2024
65d62df
Add test for trustline, disallowincoming
tequdev Feb 1, 2024
329729c
Merge branch 'develop' into featureNFTokenMintOffer
tequdev Feb 1, 2024
95b2e08
fix duplicate error handling
tequdev Feb 1, 2024
6ff088e
Merge commit '828bb64ebc76394cf9a3f7edd42f4588d106932f' into featureN…
tequdev Feb 5, 2024
bb73021
Merge remote-tracking branch 'upstream/develop' into featureNFTokenMi…
tequdev Feb 13, 2024
ffef12c
Merge commit 'af9cabe1001e9489f95ceea5f4ab4d079a1488f9' into featureN…
tequdev Mar 14, 2024
6e0d26f
Addressing Reviews
tequdev Mar 14, 2024
f65eea7
fix same as fixNFTokenTrustlineSurprise
tequdev Mar 15, 2024
fe4d616
[FOLD] Refactor NFTokenMint and NFTokenCreateOffer to share code
scottschurr Apr 29, 2024
590218b
Merge commit '40b4adc9cc296a7e3c6e8c94b5a977a54c835613' into featureN…
tequdev May 21, 2024
7bc8302
Merge branch 'develop' into featureNFTokenMintOffer
seelabs Jun 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
261 changes: 41 additions & 220 deletions src/ripple/app/tx/impl/NFTokenCreateOffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,65 +37,24 @@ NFTokenCreateOffer::preflight(PreflightContext const& ctx)
return ret;

auto const txFlags = ctx.tx.getFlags();
bool const isSellOffer = txFlags & tfSellNFToken;

if (txFlags & tfNFTokenCreateOfferMask)
return temINVALID_FLAG;

auto const account = ctx.tx[sfAccount];
auto const nftFlags = nft::getFlags(ctx.tx[sfNFTokenID]);

{
STAmount const amount = ctx.tx[sfAmount];

if (amount.negative() && ctx.rules.enabled(fixNFTokenNegOffer))
// An offer for a negative amount makes no sense.
return temBAD_AMOUNT;

if (!isXRP(amount))
{
if (nftFlags & nft::flagOnlyXRP)
return temBAD_AMOUNT;

if (!amount)
return temBAD_AMOUNT;
}

// If this is an offer to buy, you must offer something; if it's an
// offer to sell, you can ask for nothing.
if (!isSellOffer && !amount)
return temBAD_AMOUNT;
}

if (auto exp = ctx.tx[~sfExpiration]; exp == 0)
return temBAD_EXPIRATION;

auto const owner = ctx.tx[~sfOwner];

// The 'Owner' field must be present when offering to buy, but can't
// be present when selling (it's implicit):
if (owner.has_value() == isSellOffer)
return temMALFORMED;

if (owner && owner == account)
return temMALFORMED;

if (auto dest = ctx.tx[~sfDestination])
{
// Some folks think it makes sense for a buy offer to specify a
// specific broker using the Destination field. This change doesn't
// deserve it's own amendment, so we're piggy-backing on
// fixNFTokenNegOffer.
//
// Prior to fixNFTokenNegOffer any use of the Destination field on
// a buy offer was malformed.
if (!isSellOffer && !ctx.rules.enabled(fixNFTokenNegOffer))
return temMALFORMED;

// The destination can't be the account executing the transaction.
if (dest == account)
return temMALFORMED;
}
// Use implementation shared with NFTokenMint
if (NotTEC notTec = nft::tokenOfferCreatePreflight(
ctx.tx[sfAccount],
ctx.tx[sfAmount],
ctx.tx[~sfDestination],
ctx.tx[~sfExpiration],
nftFlags,
ctx.rules,
ctx.tx[~sfOwner],
txFlags);
!isTesSuccess(notTec))
return notTec;

return preflight2(ctx);
}
Expand All @@ -106,182 +65,44 @@ NFTokenCreateOffer::preclaim(PreclaimContext const& ctx)
if (hasExpired(ctx.view, ctx.tx[~sfExpiration]))
return tecEXPIRED;

auto const nftokenID = ctx.tx[sfNFTokenID];
bool const isSellOffer = ctx.tx.isFlag(tfSellNFToken);
uint256 const nftokenID = ctx.tx[sfNFTokenID];
std::uint32_t const txFlags = {ctx.tx.getFlags()};

if (!nft::findToken(
ctx.view, ctx.tx[isSellOffer ? sfAccount : sfOwner], nftokenID))
return tecNO_ENTRY;

auto const nftFlags = nft::getFlags(nftokenID);
auto const issuer = nft::getIssuer(nftokenID);
auto const amount = ctx.tx[sfAmount];

if (!(nftFlags & nft::flagCreateTrustLines) && !amount.native() &&
nft::getTransferFee(nftokenID))
{
if (!ctx.view.exists(keylet::account(issuer)))
return tecNO_ISSUER;

if (!ctx.view.exists(keylet::line(issuer, amount.issue())))
return tecNO_LINE;

if (isFrozen(
ctx.view, issuer, amount.getCurrency(), amount.getIssuer()))
return tecFROZEN;
}

if (issuer != ctx.tx[sfAccount] && !(nftFlags & nft::flagTransferable))
{
auto const root = ctx.view.read(keylet::account(issuer));
assert(root);

if (auto minter = (*root)[~sfNFTokenMinter];
minter != ctx.tx[sfAccount])
return tefNFTOKEN_IS_NOT_TRANSFERABLE;
}

if (isFrozen(
ctx.view,
ctx.tx[sfAccount],
amount.getCurrency(),
amount.getIssuer()))
return tecFROZEN;

// If this is an offer to buy the token, the account must have the
// needed funds at hand; but note that funds aren't reserved and the
// offer may later become unfunded.
if (!isSellOffer)
{
// After this amendment, we allow an IOU issuer to make a buy offer
// using their own currency.
if (ctx.view.rules().enabled(fixNonFungibleTokensV1_2))
{
if (accountFunds(
ctx.view,
ctx.tx[sfAccount],
amount,
FreezeHandling::fhZERO_IF_FROZEN,
ctx.j)
.signum() <= 0)
return tecUNFUNDED_OFFER;
}
else if (
accountHolds(
ctx.view,
ctx.tx[sfAccount],
amount.getCurrency(),
amount.getIssuer(),
FreezeHandling::fhZERO_IF_FROZEN,
ctx.j)
.signum() <= 0)
return tecUNFUNDED_OFFER;
}

if (auto const destination = ctx.tx[~sfDestination])
{
// If a destination is specified, the destination must already be in
// the ledger.
auto const sleDst = ctx.view.read(keylet::account(*destination));

if (!sleDst)
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

if (sleDst->getFlags() & lsfDisallowIncomingNFTokenOffer)
return tecNO_PERMISSION;
}
}

if (auto const owner = ctx.tx[~sfOwner])
{
// 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() & lsfDisallowIncomingNFTokenOffer)
return tecNO_PERMISSION;
}
}
ctx.tx[(txFlags & tfSellNFToken) ? sfAccount : sfOwner],
nftokenID))
return tecNO_ENTRY;

return tesSUCCESS;
// Use implementation shared with NFTokenMint
return nft::tokenOfferCreatePreclaim(
ctx.view,
ctx.tx[sfAccount],
nft::getIssuer(nftokenID),
ctx.tx[sfAmount],
ctx.tx[~sfDestination],
nft::getFlags(nftokenID),
nft::getTransferFee(nftokenID),
ctx.j,
ctx.tx[~sfOwner],
txFlags);
}

TER
NFTokenCreateOffer::doApply()
{
if (auto const acct = view().read(keylet::account(ctx_.tx[sfAccount]));
mPriorBalance < view().fees().accountReserve((*acct)[sfOwnerCount] + 1))
return tecINSUFFICIENT_RESERVE;

auto const nftokenID = ctx_.tx[sfNFTokenID];

auto const offerID =
keylet::nftoffer(account_, ctx_.tx.getSeqProxy().value());

// Create the offer:
{
// Token offers are always added to the owner's owner directory:
auto const ownerNode = view().dirInsert(
keylet::ownerDir(account_), offerID, describeOwnerDir(account_));

if (!ownerNode)
return tecDIR_FULL;

bool const isSellOffer = ctx_.tx.isFlag(tfSellNFToken);

// Token offers are also added to the token's buy or sell offer
// directory
auto const offerNode = view().dirInsert(
isSellOffer ? keylet::nft_sells(nftokenID)
: keylet::nft_buys(nftokenID),
offerID,
[&nftokenID, isSellOffer](std::shared_ptr<SLE> const& sle) {
(*sle)[sfFlags] =
isSellOffer ? lsfNFTokenSellOffers : lsfNFTokenBuyOffers;
(*sle)[sfNFTokenID] = nftokenID;
});

if (!offerNode)
return tecDIR_FULL;

std::uint32_t sleFlags = 0;

if (isSellOffer)
sleFlags |= lsfSellNFToken;

auto offer = std::make_shared<SLE>(offerID);
(*offer)[sfOwner] = account_;
(*offer)[sfNFTokenID] = nftokenID;
(*offer)[sfAmount] = ctx_.tx[sfAmount];
(*offer)[sfFlags] = sleFlags;
(*offer)[sfOwnerNode] = *ownerNode;
(*offer)[sfNFTokenOfferNode] = *offerNode;

if (auto const expiration = ctx_.tx[~sfExpiration])
(*offer)[sfExpiration] = *expiration;

if (auto const destination = ctx_.tx[~sfDestination])
(*offer)[sfDestination] = *destination;

view().insert(offer);
}

// Update owner count.
adjustOwnerCount(view(), view().peek(keylet::account(account_)), 1, j_);

return tesSUCCESS;
// Use implementation shared with NFTokenMint
return nft::tokenOfferCreateApply(
view(),
ctx_.tx[sfAccount],
ctx_.tx[sfAmount],
ctx_.tx[~sfDestination],
ctx_.tx[~sfExpiration],
ctx_.tx.getSeqProxy(),
ctx_.tx[sfNFTokenID],
mPriorBalance,
j_,
ctx_.tx.getFlags());
}

} // namespace ripple
Loading
Loading