xrpld
Loading...
Searching...
No Matches
NFTokenMint.cpp
1#include <xrpl/tx/transactors/nft/NFTokenMint.h>
2
3#include <xrpl/basics/base_uint.h>
4#include <xrpl/beast/utility/instrumentation.h>
5#include <xrpl/ledger/View.h>
6#include <xrpl/ledger/helpers/AccountRootHelpers.h>
7#include <xrpl/ledger/helpers/NFTokenHelpers.h>
8#include <xrpl/protocol/AccountID.h>
9#include <xrpl/protocol/Feature.h>
10#include <xrpl/protocol/Indexes.h>
11#include <xrpl/protocol/InnerObjectFormats.h>
12#include <xrpl/protocol/Protocol.h>
13#include <xrpl/protocol/SField.h>
14#include <xrpl/protocol/SOTemplate.h>
15#include <xrpl/protocol/STLedgerEntry.h>
16#include <xrpl/protocol/STObject.h>
17#include <xrpl/protocol/STTx.h>
18#include <xrpl/protocol/TER.h>
19#include <xrpl/protocol/TxFlags.h>
20#include <xrpl/protocol/XRPAmount.h>
21#include <xrpl/protocol/nft.h>
22#include <xrpl/tx/Transactor.h>
23
24#include <boost/endian/conversion.hpp>
25
26#include <array>
27#include <cstdint>
28#include <cstring>
29#include <expected>
30#include <iterator> // IWYU pragma: keep
31#include <utility>
32
33namespace xrpl {
34
35static std::uint16_t
37{
38 return static_cast<std::uint16_t>(txFlags & 0x0000FFFF);
39}
40
41static bool
43{
44 return ctx.tx.isFieldPresent(sfAmount) || ctx.tx.isFieldPresent(sfDestination) ||
45 ctx.tx.isFieldPresent(sfExpiration);
46}
47
48bool
50{
51 return ctx.rules.enabled(featureNFTokenMintOffer) || !hasOfferFields(ctx);
52}
53
56{
57 // Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between
58 // accounts allowed a TrustLine to be added to the issuer of that token
59 // without explicit permission from that issuer. This was enabled by
60 // minting the NFToken with the tfTrustLine flag set.
61 //
62 // That capability could be used to attack the NFToken issuer. It
63 // would be possible for two accounts to trade the NFToken back and forth
64 // building up any number of TrustLines on the issuer, increasing the
65 // issuer's reserve without bound.
66 //
67 // The fixRemoveNFTokenAutoTrustLine amendment disables minting with the
68 // tfTrustLine flag as a way to prevent the attack. But until the
69 // amendment passes we still need to keep the old behavior available.
70 std::uint32_t const nfTokenMintMask = [&]() -> std::uint32_t {
71 if (ctx.rules.enabled(fixRemoveNFTokenAutoTrustLine))
72 {
73 // if featureDynamicNFT enabled then new flag allowing mutable URI available
74 return ctx.rules.enabled(featureDynamicNFT) ? tfNFTokenMintMask
76 }
77 return ctx.rules.enabled(featureDynamicNFT) ? tfNFTokenMintOldMaskWithMutable
79 }();
80
81 return nfTokenMintMask;
82}
83
86{
87 if (auto const f = ctx.tx[~sfTransferFee])
88 {
89 if (f > kMaxTransferFee)
91
92 // If a non-zero TransferFee is set then the tfTransferable flag
93 // must also be set.
94 if (f > 0u && !ctx.tx.isFlag(tfTransferable))
95 return temMALFORMED;
96 }
97
98 // An issuer must only be set if the tx is executed by the minter
99 if (auto iss = ctx.tx[~sfIssuer]; iss == ctx.tx[sfAccount])
100 return temMALFORMED;
101
102 if (auto uri = ctx.tx[~sfURI])
103 {
104 if (uri->empty() || uri->length() > kMaxTokenUriLength)
105 return temMALFORMED;
106 }
107
108 if (hasOfferFields(ctx))
109 {
110 // The Amount field must be present if either the Destination or
111 // Expiration fields are present.
112 if (!ctx.tx.isFieldPresent(sfAmount))
113 return temMALFORMED;
114
115 // Rely on the common code shared with NFTokenCreateOffer to
116 // do the validation. We pass tfSellNFToken as the transaction flags
117 // because a Mint is only allowed to create a sell offer.
119 ctx.tx[sfAccount],
120 ctx.tx[sfAmount],
121 ctx.tx[~sfDestination],
122 ctx.tx[~sfExpiration],
124 ctx.rules);
125 !isTesSuccess(notTec))
126 {
127 return notTec;
128 }
129 }
130
131 return tesSUCCESS;
132}
133
136 std::uint16_t flags,
137 std::uint16_t fee,
138 AccountID const& issuer,
139 nft::Taxon taxon,
140 std::uint32_t tokenSeq)
141{
142 // An issuer may issue several NFTs with the same taxon; to ensure that NFTs
143 // are spread across multiple pages we lightly mix the taxon up by using the
144 // sequence (which is not under the issuer's direct control) as the seed for
145 // a simple linear congruential generator. cipheredTaxon() does this work.
146 taxon = nft::cipheredTaxon(tokenSeq, taxon);
147
148 // The values are packed inside a 32-byte buffer, so we need to make sure
149 // that the endianess is fixed.
150 flags = boost::endian::native_to_big(flags);
151 fee = boost::endian::native_to_big(fee);
152 taxon = nft::toTaxon(boost::endian::native_to_big(nft::toUInt32(taxon)));
153 tokenSeq = boost::endian::native_to_big(tokenSeq);
154
156
157 auto ptr = buf.data();
158
159 // This code is awkward but the idea is to pack these values into a single
160 // 256-bit value that uniquely identifies this NFT.
161 std::memcpy(ptr, &flags, sizeof(flags));
162 ptr += sizeof(flags);
163
164 std::memcpy(ptr, &fee, sizeof(fee));
165 ptr += sizeof(fee);
166
167 std::memcpy(ptr, issuer.data(), issuer.size());
168 ptr += issuer.size();
169
170 std::memcpy(ptr, &taxon, sizeof(taxon));
171 ptr += sizeof(taxon);
172
173 std::memcpy(ptr, &tokenSeq, sizeof(tokenSeq));
174 ptr += sizeof(tokenSeq);
175 XRPL_ASSERT(
176 std::distance(buf.data(), ptr) == buf.size(),
177 "xrpl::NFTokenMint::createNFTokenID : data size matches the buffer");
178
179 return uint256::fromVoid(buf.data());
180}
181
182TER
184{
185 // The issuer of the NFT may or may not be the account executing this
186 // transaction. Check that and verify that this is allowed:
187 if (auto issuer = ctx.tx[~sfIssuer])
188 {
189 auto const sle = ctx.view.read(keylet::account(*issuer));
190
191 if (!sle)
192 return tecNO_ISSUER;
193
194 if (auto const minter = (*sle)[~sfNFTokenMinter]; minter != ctx.tx[sfAccount])
195 return tecNO_PERMISSION;
196 }
197
198 if (ctx.tx.isFieldPresent(sfAmount))
199 {
200 // The Amount field says create an offer for the minted token.
201 if (hasExpired(ctx.view, ctx.tx[~sfExpiration]))
202 return tecEXPIRED;
203
204 // Rely on the common code shared with NFTokenCreateOffer to
205 // do the validation. We pass tfSellNFToken as the transaction flags
206 // because a Mint is only allowed to create a sell offer.
207 if (TER const ter = nft::tokenOfferCreatePreclaim(
208 ctx.view,
209 ctx.tx[sfAccount],
210 ctx.tx[~sfIssuer].value_or(ctx.tx[sfAccount]),
211 ctx.tx[sfAmount],
212 ctx.tx[~sfDestination],
214 ctx.tx[~sfTransferFee].value_or(0),
215 ctx.j);
216 !isTesSuccess(ter))
217 return ter;
218 }
219 return tesSUCCESS;
220}
221
222TER
224{
225 auto const issuer = ctx_.tx[~sfIssuer].value_or(accountID_);
226
227 auto const tokenSeq = [this, &issuer]() -> std::expected<std::uint32_t, TER> {
228 auto const root = view().peek(keylet::account(issuer));
229 if (root == nullptr)
230 {
231 // Should not happen. Checked in preclaim.
233 }
234
235 // If the issuer hasn't minted an NFToken before we must add a
236 // FirstNFTokenSequence field to the issuer's AccountRoot. The
237 // value of the FirstNFTokenSequence must equal the issuer's
238 // current account sequence.
239 //
240 // There are three situations:
241 // o If the first token is being minted by the issuer and
242 // * If the transaction consumes a Sequence number, then the
243 // Sequence has been pre-incremented by the time we get here in
244 // doApply. We must decrement the value in the Sequence field.
245 // * Otherwise the transaction uses a Ticket so the Sequence has
246 // not been pre-incremented. We use the Sequence value as is.
247 // o The first token is being minted by an authorized minter. In
248 // this case the issuer's Sequence field has been left untouched.
249 // We use the issuer's Sequence value as is.
250 if (!root->isFieldPresent(sfFirstNFTokenSequence))
251 {
252 std::uint32_t const acctSeq = root->at(sfSequence);
253
254 root->at(sfFirstNFTokenSequence) =
255 ctx_.tx.isFieldPresent(sfIssuer) || ctx_.tx.getSeqProxy().isTicket() ? acctSeq
256 : acctSeq - 1;
257 }
258
259 std::uint32_t const mintedNftCnt = (*root)[~sfMintedNFTokens].valueOr(0u);
260
261 (*root)[sfMintedNFTokens] = mintedNftCnt + 1u;
262 if ((*root)[sfMintedNFTokens] == 0u)
264
265 // Get the unique sequence number of this token by
266 // sfFirstNFTokenSequence + sfMintedNFTokens
267 std::uint32_t const offset = (*root)[sfFirstNFTokenSequence];
268 std::uint32_t const tokenSeq = offset + mintedNftCnt;
269
270 // Check for more overflow cases
271 if (tokenSeq + 1u == 0u || tokenSeq < offset)
273
274 ctx_.view().update(root);
275 return tokenSeq;
276 }();
277
278 if (!tokenSeq.has_value())
279 return (tokenSeq.error());
280
281 std::uint32_t const ownerCountBefore =
282 view().read(keylet::account(accountID_))->getFieldU32(sfOwnerCount);
283
284 // Assemble the new NFToken.
285 SOTemplate const* nfTokenTemplate =
287
288 if (nfTokenTemplate == nullptr)
289 {
290 // Should never happen.
291 return tecINTERNAL; // LCOV_EXCL_LINE
292 }
293
294 auto const nftokenID = createNFTokenID(
296 ctx_.tx[~sfTransferFee].value_or(0),
297 issuer,
298 nft::toTaxon(ctx_.tx[sfNFTokenTaxon]),
299 tokenSeq.value());
300
301 STObject newToken(*nfTokenTemplate, sfNFToken, [this, &nftokenID](STObject& object) {
302 object.setFieldH256(sfNFTokenID, nftokenID);
303
304 if (auto const uri = ctx_.tx[~sfURI])
305 object.setFieldVL(sfURI, *uri);
306 });
307
308 if (TER const ret = nft::insertToken(ctx_.view(), accountID_, std::move(newToken));
309 !isTesSuccess(ret))
310 return ret;
311
312 if (ctx_.tx.isFieldPresent(sfAmount))
313 {
314 // Rely on the common code shared with NFTokenCreateOffer to create
315 // the offer. We pass tfSellNFToken as the transaction flags
316 // because a Mint is only allowed to create a sell offer.
317 if (TER const ter = nft::tokenOfferCreateApply(
318 view(),
319 ctx_.tx[sfAccount],
320 ctx_.tx[sfAmount],
321 ctx_.tx[~sfDestination],
322 ctx_.tx[~sfExpiration],
323 ctx_.tx.getSeqProxy(),
324 nftokenID,
326 j_);
327 !isTesSuccess(ter))
328 return ter;
329 }
330
331 // Only check the reserve if the owner count actually changed. This
332 // allows NFTs to be added to the page (and burn fees) without
333 // requiring the reserve to be met each time. The reserve is
334 // only managed when a new NFT page or sell offer is added.
335 auto const sleAccount = view().read(keylet::account(accountID_));
336 if (!sleAccount)
337 return tefINTERNAL; // LCOV_EXCL_LINE
338
339 if (auto const ownerCountAfter = sleAccount->getFieldU32(sfOwnerCount);
340 ownerCountAfter > ownerCountBefore)
341 {
342 if (preFeeBalance_ < accountReserve(view(), sleAccount, j_))
344 }
345 return tesSUCCESS;
346}
347
348void
350{
351 // No transaction-specific invariants yet (future work).
352}
353
354bool
356{
357 // No transaction-specific invariants yet (future work).
358 return true;
359}
360
361} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
virtual SLE::pointer peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
static BaseUInt fromVoid(void const *data)
Definition base_uint.h:339
pointer data()
Definition base_uint.h:117
static constexpr std::size_t size()
Definition base_uint.h:548
SOTemplate const * findSOTemplateBySField(SField const &sField) const
static InnerObjectFormats const & getInstance()
static uint256 createNFTokenID(std::uint16_t flags, std::uint16_t fee, AccountID const &issuer, nft::Taxon taxon, std::uint32_t tokenSeq)
bool finalizeInvariants(STTx const &tx, TER result, XRPAmount fee, ReadView const &view, beast::Journal const &j) override
Check transaction-specific post-conditions after all entries have been visited.
static NotTEC preflight(PreflightContext const &ctx)
void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override
Inspect a single ledger entry modified by this transaction.
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
TER doApply() override
static TER preclaim(PreclaimContext const &ctx)
static bool checkExtraFeatures(PreflightContext const &ctx)
A view into a ledger.
Definition ReadView.h:41
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:180
Defines the fields and their attributes within a STObject.
Definition SOTemplate.h:105
std::shared_ptr< STLedgerEntry const > const & const_ref
bool isFlag(std::uint32_t) const
Definition STObject.cpp:511
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:464
std::uint32_t getFlags() const
Definition STObject.cpp:517
beast::Journal const j_
Definition Transactor.h:155
ApplyView & view()
Definition Transactor.h:175
AccountID const accountID_
Definition Transactor.h:157
XRPAmount preFeeBalance_
Definition Transactor.h:158
ApplyContext & ctx_
Definition Transactor.h:153
T data(T... args)
T distance(T... args)
T memcpy(T... args)
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:198
std::uint32_t toUInt32(Taxon t)
Definition nft.h:27
TER tokenOfferCreatePreclaim(ReadView const &view, AccountID const &acctID, AccountID const &nftIssuer, STAmount const &amount, std::optional< AccountID > const &dest, std::uint16_t nftFlags, std::uint16_t xferFee, beast::Journal j, std::optional< AccountID > const &owner=std::nullopt, std::uint32_t txFlags=tfSellNFToken)
Preclaim checks shared by NFTokenCreateOffer and NFTokenMint.
TER insertToken(ApplyView &view, AccountID owner, STObject &&nft)
Insert the token in the owner's token directory.
Taxon toTaxon(std::uint32_t i)
Definition nft.h:21
Taxon cipheredTaxon(std::uint32_t tokenSeq, Taxon taxon)
Definition nft.h:63
TER tokenOfferCreateApply(ApplyView &view, AccountID const &acctID, STAmount const &amount, std::optional< AccountID > const &dest, std::optional< std::uint32_t > const &expiration, SeqProxy seqProxy, uint256 const &nftokenID, XRPAmount const &priorBalance, beast::Journal j, std::uint32_t txFlags=tfSellNFToken)
doApply implementation shared by NFTokenCreateOffer and NFTokenMint
NotTEC tokenOfferCreatePreflight(AccountID const &acctID, STAmount const &amount, std::optional< AccountID > const &dest, std::optional< std::uint32_t > const &expiration, std::uint16_t nftFlags, Rules const &rules, std::optional< AccountID > const &owner=std::nullopt, std::uint32_t txFlags=tfSellNFToken)
Preflight checks shared by NFTokenCreateOffer and NFTokenMint.
TaggedInteger< std::uint32_t, TaxonTag > Taxon
Definition nft.h:18
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp, ExpiryComparison comparison=ExpiryComparison::Inclusive)
Determines whether the given expiration time has passed.
Definition View.cpp:48
constexpr std::size_t kMaxTokenUriLength
The maximum length of a URI inside an NFT.
Definition Protocol.h:245
constexpr FlagValue tfNFTokenMintMaskWithoutMutable
Definition TxFlags.h:399
@ tefINTERNAL
Definition TER.h:165
constexpr FlagValue tfNFTokenMintOldMaskWithMutable
Definition TxFlags.h:405
static bool hasOfferFields(PreflightContext const &ctx)
Number root(Number f, unsigned d)
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:607
constexpr FlagValue tfNFTokenMintOldMask
Definition TxFlags.h:402
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
constexpr std::uint16_t kMaxTransferFee
The maximum token transfer fee allowed.
Definition Protocol.h:96
@ temMALFORMED
Definition TER.h:75
@ temBAD_NFTOKEN_TRANSFER_FEE
Definition TER.h:115
bool isTesSuccess(TER x) noexcept
Definition TER.h:676
TERSubset< CanCvtToTER > TER
Definition TER.h:647
@ tecINTERNAL
Definition TER.h:313
@ tecEXPIRED
Definition TER.h:317
@ tecINSUFFICIENT_RESERVE
Definition TER.h:310
@ tecMAX_SEQUENCE_REACHED
Definition TER.h:323
@ tecNO_PERMISSION
Definition TER.h:308
@ tecNO_ISSUER
Definition TER.h:302
BaseUInt< 256 > uint256
Definition base_uint.h:580
static std::uint16_t extractNFTokenFlagsFromTxFlags(std::uint32_t txFlags)
XRPAmount accountReserve(ReadView const &view, SLE::const_ref sle, beast::Journal j, Adjustment adj={})
Returns the account reserve, in drops.
@ tesSUCCESS
Definition TER.h:245
T size(T... args)
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:83
ReadView const & view
Definition Transactor.h:86
beast::Journal const j
Definition Transactor.h:91
State information when preflighting a tx.
Definition Transactor.h:38
T unexpected(T... args)