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 21 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
167 changes: 156 additions & 11 deletions src/ripple/app/tx/impl/NFTokenMint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ NFTokenMint::preflight(PreflightContext const& ctx)
if (!ctx.rules.enabled(featureNonFungibleTokensV1))
return temDISABLED;

if (!ctx.rules.enabled(featureNFTokenMintOffer) &&
(ctx.tx.isFieldPresent(sfAmount) ||
ctx.tx.isFieldPresent(sfDestination) ||
ctx.tx.isFieldPresent(sfExpiration)))
return temDISABLED;

if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;

Expand Down Expand Up @@ -80,6 +86,40 @@ NFTokenMint::preflight(PreflightContext const& ctx)
return temMALFORMED;
}

// Amount field should be set if Destination and/or Expiration is set
if (ctx.tx.isFieldPresent(sfDestination) ||
ctx.tx.isFieldPresent(sfExpiration))
{
if (!ctx.tx.isFieldPresent(sfAmount))
return temMALFORMED;
}

if (auto 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 (ctx.tx.isFlag(tfOnlyXRP))
return temBAD_AMOUNT;

if (!*amount)
return temBAD_AMOUNT;
}
}

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

if (auto const dest = ctx.tx[~sfDestination])
{
// The destination can't be the account executing the transaction.
if (dest == ctx.tx[sfAccount])
return temMALFORMED;
}

return preflight2(ctx);
}

Expand Down Expand Up @@ -146,6 +186,57 @@ NFTokenMint::preclaim(PreclaimContext const& ctx)
return tecNO_PERMISSION;
}

if (auto const amount = ctx.tx[~sfAmount])
{
if (hasExpired(ctx.view, ctx.tx[~sfExpiration]))
return tecEXPIRED;

if (!(ctx.tx.getFlags() & nft::flagCreateTrustLines) &&
!amount->native() && ctx.tx[~sfTransferFee])
{
auto const nftIssuer =
ctx.tx[~sfIssuer].value_or(ctx.tx[sfAccount]);

// If the IOU issuer and the NFToken issuer are the same,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commit message should be prefixed with [FOLD] to indicate that this commit should be merged (likely with the previous commit).

// then that issuer does not need a trust line to accept their fee.
if (nftIssuer != amount->getIssuer() &&
!ctx.view.read(keylet::line(nftIssuer, amount->issue())))
return tecNO_LINE;

if (isFrozen(
ctx.view,
nftIssuer,
amount->getCurrency(),
amount->getIssuer()))
return tecFROZEN;
}

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

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;
}
}
}
return tesSUCCESS;
}

Expand Down Expand Up @@ -238,18 +329,16 @@ NFTokenMint::doApply()
// Should never happen.
return tecINTERNAL;

auto const nftokenID = createNFTokenID(
static_cast<std::uint16_t>(ctx_.tx.getFlags() & 0x0000FFFF),
ctx_.tx[~sfTransferFee].value_or(0),
issuer,
nft::toTaxon(ctx_.tx[sfNFTokenTaxon]),
tokenSeq.value());

STObject newToken(
*nfTokenTemplate,
sfNFToken,
[this, &issuer, &tokenSeq](STObject& object) {
object.setFieldH256(
sfNFTokenID,
createNFTokenID(
static_cast<std::uint16_t>(ctx_.tx.getFlags() & 0x0000FFFF),
ctx_.tx[~sfTransferFee].value_or(0),
issuer,
nft::toTaxon(ctx_.tx[sfNFTokenTaxon]),
tokenSeq.value()));
*nfTokenTemplate, sfNFToken, [this, &nftokenID](STObject& object) {
object.setFieldH256(sfNFTokenID, nftokenID);

if (auto const uri = ctx_.tx[~sfURI])
object.setFieldVL(sfURI, *uri);
Expand All @@ -260,6 +349,62 @@ NFTokenMint::doApply()
ret != tesSUCCESS)
return ret;

if (ctx_.tx.isFieldPresent(sfAmount))
{
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 = ctx_.view().dirInsert(
keylet::ownerDir(account_),
offerID,
describeOwnerDir(account_));

if (!ownerNode)
return tecDIR_FULL;

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

if (!offerNode)
return tecDIR_FULL;

// Only sell offer are supported at NFTokenMint.
std::uint32_t const 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;

ctx_.view().insert(offer);

adjustOwnerCount(
ctx_.view(),
ctx_.view().peek(keylet::account(account_)),
1,
j_);
}
}

// Only check the reserve if the owner count actually changed. This
// allows NFTs to be added to the page (and burn fees) without
// requiring the reserve to be met each time. The reserve is
Expand Down
3 changes: 2 additions & 1 deletion src/ripple/protocol/Feature.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ namespace detail {
// Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 68;
static constexpr std::size_t numFeatures = 69;

/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated
Expand Down Expand Up @@ -355,6 +355,7 @@ extern uint256 const fixFillOrKill;
extern uint256 const fixNFTokenReserve;
extern uint256 const fixInnerObjTemplate;
extern uint256 const featurePriceOracle;
extern uint256 const featureNFTokenMintOffer;

} // namespace ripple

Expand Down
5 changes: 3 additions & 2 deletions src/ripple/protocol/impl/Feature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -458,10 +458,11 @@ REGISTER_FEATURE(AMM, Supported::yes, VoteBehavior::De
REGISTER_FEATURE(XChainBridge, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX (fixDisallowIncomingV1, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(DID, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX(fixFillOrKill, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX (fixFillOrKill, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX (fixNFTokenReserve, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX(fixInnerObjTemplate, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX (fixInnerObjTemplate, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(PriceOracle, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(NFTokenMintOffer, Supported::yes, VoteBehavior::DefaultNo);

// The following amendments are obsolete, but must remain supported
// because they could potentially get enabled.
Expand Down
3 changes: 2 additions & 1 deletion src/ripple/protocol/impl/NFTokenOfferID.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ canHaveNFTokenOfferID(
return false;

TxType const tt = serializedTx->getTxnType();
if (tt != ttNFTOKEN_CREATE_OFFER)
if (!(tt == ttNFTOKEN_MINT && serializedTx->isFieldPresent(sfAmount)) &&
tt != ttNFTOKEN_CREATE_OFFER)
return false;

// if the transaction failed nothing could have been delivered.
Expand Down
3 changes: 3 additions & 0 deletions src/ripple/protocol/impl/TxFormats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,9 @@ TxFormats::TxFormats()
{sfTransferFee, soeOPTIONAL},
{sfIssuer, soeOPTIONAL},
{sfURI, soeOPTIONAL},
{sfAmount, soeOPTIONAL},
{sfDestination, soeOPTIONAL},
{sfExpiration, soeOPTIONAL},
},
commonFields);

Expand Down
Loading
Loading