Skip to content

Commit

Permalink
[FOLD] Refactor NFTokenMint and NFTokenCreateOffer to share code
Browse files Browse the repository at this point in the history
  • Loading branch information
scottschurr committed Apr 29, 2024
1 parent f65eea7 commit 8f85084
Show file tree
Hide file tree
Showing 5 changed files with 527 additions and 368 deletions.
244 changes: 24 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,16 @@ 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, nftFlags, txFlags);
!isTesSuccess(notTec))
return notTec;

return preflight2(ctx);
}
Expand All @@ -106,182 +57,35 @@ 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,
nft::getIssuer(nftokenID),
nft::getFlags(nftokenID),
nft::getTransferFee(nftokenID),
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(
ctx_,
view(),
ctx_.tx[sfNFTokenID],
mPriorBalance,
ctx_.tx.getFlags(),
j_);
}

} // namespace ripple
Loading

0 comments on commit 8f85084

Please sign in to comment.