rippled
Loading...
Searching...
No Matches
NFTokenMint.cpp
1#include <xrpl/basics/Expected.h>
2#include <xrpl/ledger/View.h>
3#include <xrpl/protocol/Feature.h>
4#include <xrpl/protocol/InnerObjectFormats.h>
5#include <xrpl/protocol/Rate.h>
6#include <xrpl/protocol/TxFlags.h>
7#include <xrpl/tx/transactors/nft/NFTokenMint.h>
8
9#include <boost/endian/conversion.hpp>
10
11#include <array>
12
13namespace xrpl {
14
15static std::uint16_t
17{
18 return static_cast<std::uint16_t>(txFlags & 0x0000FFFF);
19}
20
21static bool
23{
24 return ctx.tx.isFieldPresent(sfAmount) || ctx.tx.isFieldPresent(sfDestination) ||
25 ctx.tx.isFieldPresent(sfExpiration);
26}
27
28bool
30{
31 return ctx.rules.enabled(featureNFTokenMintOffer) || !hasOfferFields(ctx);
32}
33
36{
37 // Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between
38 // accounts allowed a TrustLine to be added to the issuer of that token
39 // without explicit permission from that issuer. This was enabled by
40 // minting the NFToken with the tfTrustLine flag set.
41 //
42 // That capability could be used to attack the NFToken issuer. It
43 // would be possible for two accounts to trade the NFToken back and forth
44 // building up any number of TrustLines on the issuer, increasing the
45 // issuer's reserve without bound.
46 //
47 // The fixRemoveNFTokenAutoTrustLine amendment disables minting with the
48 // tfTrustLine flag as a way to prevent the attack. But until the
49 // amendment passes we still need to keep the old behavior available.
50 std::uint32_t const nfTokenMintMask = [&]() -> std::uint32_t {
51 if (ctx.rules.enabled(fixRemoveNFTokenAutoTrustLine))
52 {
53 // if featureDynamicNFT enabled then new flag allowing mutable URI available
54 return ctx.rules.enabled(featureDynamicNFT) ? tfNFTokenMintMask
56 }
57 return ctx.rules.enabled(featureDynamicNFT) ? tfNFTokenMintOldMaskWithMutable
59 }();
60
61 return nfTokenMintMask;
62}
63
66{
67 if (auto const f = ctx.tx[~sfTransferFee])
68 {
69 if (f > maxTransferFee)
71
72 // If a non-zero TransferFee is set then the tfTransferable flag
73 // must also be set.
74 if (f > 0u && !ctx.tx.isFlag(tfTransferable))
75 return temMALFORMED;
76 }
77
78 // An issuer must only be set if the tx is executed by the minter
79 if (auto iss = ctx.tx[~sfIssuer]; iss == ctx.tx[sfAccount])
80 return temMALFORMED;
81
82 if (auto uri = ctx.tx[~sfURI])
83 {
84 if (uri->empty() || uri->length() > maxTokenURILength)
85 return temMALFORMED;
86 }
87
88 if (hasOfferFields(ctx))
89 {
90 // The Amount field must be present if either the Destination or
91 // Expiration fields are present.
92 if (!ctx.tx.isFieldPresent(sfAmount))
93 return temMALFORMED;
94
95 // Rely on the common code shared with NFTokenCreateOffer to
96 // do the validation. We pass tfSellNFToken as the transaction flags
97 // because a Mint is only allowed to create a sell offer.
99 ctx.tx[sfAccount],
100 ctx.tx[sfAmount],
101 ctx.tx[~sfDestination],
102 ctx.tx[~sfExpiration],
104 ctx.rules);
105 !isTesSuccess(notTec))
106 {
107 return notTec;
108 }
109 }
110
111 return tesSUCCESS;
112}
113
116 std::uint16_t flags,
117 std::uint16_t fee,
118 AccountID const& issuer,
119 nft::Taxon taxon,
120 std::uint32_t tokenSeq)
121{
122 // An issuer may issue several NFTs with the same taxon; to ensure that NFTs
123 // are spread across multiple pages we lightly mix the taxon up by using the
124 // sequence (which is not under the issuer's direct control) as the seed for
125 // a simple linear congruential generator. cipheredTaxon() does this work.
126 taxon = nft::cipheredTaxon(tokenSeq, taxon);
127
128 // The values are packed inside a 32-byte buffer, so we need to make sure
129 // that the endianess is fixed.
130 flags = boost::endian::native_to_big(flags);
131 fee = boost::endian::native_to_big(fee);
132 taxon = nft::toTaxon(boost::endian::native_to_big(nft::toUInt32(taxon)));
133 tokenSeq = boost::endian::native_to_big(tokenSeq);
134
136
137 auto ptr = buf.data();
138
139 // This code is awkward but the idea is to pack these values into a single
140 // 256-bit value that uniquely identifies this NFT.
141 std::memcpy(ptr, &flags, sizeof(flags));
142 ptr += sizeof(flags);
143
144 std::memcpy(ptr, &fee, sizeof(fee));
145 ptr += sizeof(fee);
146
147 std::memcpy(ptr, issuer.data(), issuer.size());
148 ptr += issuer.size();
149
150 std::memcpy(ptr, &taxon, sizeof(taxon));
151 ptr += sizeof(taxon);
152
153 std::memcpy(ptr, &tokenSeq, sizeof(tokenSeq));
154 ptr += sizeof(tokenSeq);
155 XRPL_ASSERT(
156 std::distance(buf.data(), ptr) == buf.size(),
157 "xrpl::NFTokenMint::createNFTokenID : data size matches the buffer");
158
159 return uint256::fromVoid(buf.data());
160}
161
162TER
164{
165 // The issuer of the NFT may or may not be the account executing this
166 // transaction. Check that and verify that this is allowed:
167 if (auto issuer = ctx.tx[~sfIssuer])
168 {
169 auto const sle = ctx.view.read(keylet::account(*issuer));
170
171 if (!sle)
172 return tecNO_ISSUER;
173
174 if (auto const minter = (*sle)[~sfNFTokenMinter]; minter != ctx.tx[sfAccount])
175 return tecNO_PERMISSION;
176 }
177
178 if (ctx.tx.isFieldPresent(sfAmount))
179 {
180 // The Amount field says create an offer for the minted token.
181 if (hasExpired(ctx.view, ctx.tx[~sfExpiration]))
182 return tecEXPIRED;
183
184 // Rely on the common code shared with NFTokenCreateOffer to
185 // do the validation. We pass tfSellNFToken as the transaction flags
186 // because a Mint is only allowed to create a sell offer.
187 if (TER const ter = nft::tokenOfferCreatePreclaim(
188 ctx.view,
189 ctx.tx[sfAccount],
190 ctx.tx[~sfIssuer].value_or(ctx.tx[sfAccount]),
191 ctx.tx[sfAmount],
192 ctx.tx[~sfDestination],
194 ctx.tx[~sfTransferFee].value_or(0),
195 ctx.j);
196 !isTesSuccess(ter))
197 return ter;
198 }
199 return tesSUCCESS;
200}
201
202TER
204{
205 auto const issuer = ctx_.tx[~sfIssuer].value_or(account_);
206
207 auto const tokenSeq = [this, &issuer]() -> Expected<std::uint32_t, TER> {
208 auto const root = view().peek(keylet::account(issuer));
209 if (root == nullptr)
210 {
211 // Should not happen. Checked in preclaim.
212 return Unexpected(tecNO_ISSUER);
213 }
214
215 // If the issuer hasn't minted an NFToken before we must add a
216 // FirstNFTokenSequence field to the issuer's AccountRoot. The
217 // value of the FirstNFTokenSequence must equal the issuer's
218 // current account sequence.
219 //
220 // There are three situations:
221 // o If the first token is being minted by the issuer and
222 // * If the transaction consumes a Sequence number, then the
223 // Sequence has been pre-incremented by the time we get here in
224 // doApply. We must decrement the value in the Sequence field.
225 // * Otherwise the transaction uses a Ticket so the Sequence has
226 // not been pre-incremented. We use the Sequence value as is.
227 // o The first token is being minted by an authorized minter. In
228 // this case the issuer's Sequence field has been left untouched.
229 // We use the issuer's Sequence value as is.
230 if (!root->isFieldPresent(sfFirstNFTokenSequence))
231 {
232 std::uint32_t const acctSeq = root->at(sfSequence);
233
234 root->at(sfFirstNFTokenSequence) =
235 ctx_.tx.isFieldPresent(sfIssuer) || ctx_.tx.getSeqProxy().isTicket() ? acctSeq
236 : acctSeq - 1;
237 }
238
239 std::uint32_t const mintedNftCnt = (*root)[~sfMintedNFTokens].value_or(0u);
240
241 (*root)[sfMintedNFTokens] = mintedNftCnt + 1u;
242 if ((*root)[sfMintedNFTokens] == 0u)
244
245 // Get the unique sequence number of this token by
246 // sfFirstNFTokenSequence + sfMintedNFTokens
247 std::uint32_t const offset = (*root)[sfFirstNFTokenSequence];
248 std::uint32_t const tokenSeq = offset + mintedNftCnt;
249
250 // Check for more overflow cases
251 if (tokenSeq + 1u == 0u || tokenSeq < offset)
253
254 ctx_.view().update(root);
255 return tokenSeq;
256 }();
257
258 if (!tokenSeq.has_value())
259 return (tokenSeq.error());
260
261 std::uint32_t const ownerCountBefore =
262 view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
263
264 // Assemble the new NFToken.
265 SOTemplate const* nfTokenTemplate =
267
268 if (nfTokenTemplate == nullptr)
269 {
270 // Should never happen.
271 return tecINTERNAL; // LCOV_EXCL_LINE
272 }
273
274 auto const nftokenID = createNFTokenID(
276 ctx_.tx[~sfTransferFee].value_or(0),
277 issuer,
278 nft::toTaxon(ctx_.tx[sfNFTokenTaxon]),
279 tokenSeq.value());
280
281 STObject newToken(*nfTokenTemplate, sfNFToken, [this, &nftokenID](STObject& object) {
282 object.setFieldH256(sfNFTokenID, nftokenID);
283
284 if (auto const uri = ctx_.tx[~sfURI])
285 object.setFieldVL(sfURI, *uri);
286 });
287
288 if (TER const ret = nft::insertToken(ctx_.view(), account_, std::move(newToken));
289 !isTesSuccess(ret))
290 return ret;
291
292 if (ctx_.tx.isFieldPresent(sfAmount))
293 {
294 // Rely on the common code shared with NFTokenCreateOffer to create
295 // the offer. We pass tfSellNFToken as the transaction flags
296 // because a Mint is only allowed to create a sell offer.
297 if (TER const ter = nft::tokenOfferCreateApply(
298 view(),
299 ctx_.tx[sfAccount],
300 ctx_.tx[sfAmount],
301 ctx_.tx[~sfDestination],
302 ctx_.tx[~sfExpiration],
304 nftokenID,
306 j_);
307 !isTesSuccess(ter))
308 return ter;
309 }
310
311 // Only check the reserve if the owner count actually changed. This
312 // allows NFTs to be added to the page (and burn fees) without
313 // requiring the reserve to be met each time. The reserve is
314 // only managed when a new NFT page or sell offer is added.
315 if (auto const ownerCountAfter =
316 view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
317 ownerCountAfter > ownerCountBefore)
318 {
319 if (auto const reserve = view().fees().accountReserve(ownerCountAfter);
320 preFeeBalance_ < reserve)
322 }
323 return tesSUCCESS;
324}
325
326} // namespace xrpl
STTx const & tx
ApplyView & view()
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
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)
static NotTEC preflight(PreflightContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
TER doApply() override
static TER preclaim(PreclaimContext const &ctx)
static bool checkExtraFeatures(PreflightContext const &ctx)
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual std::shared_ptr< SLE const > 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:120
Defines the fields and their attributes within a STObject.
Definition SOTemplate.h:92
bool isFlag(std::uint32_t) const
Definition STObject.cpp:503
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:456
std::uint32_t getFlags() const
Definition STObject.cpp:509
SeqProxy getSeqProxy() const
Definition STTx.cpp:194
constexpr bool isTicket() const
Definition SeqProxy.h:74
AccountID const account_
Definition Transactor.h:116
beast::Journal const j_
Definition Transactor.h:114
ApplyView & view()
Definition Transactor.h:132
XRPAmount preFeeBalance_
Definition Transactor.h:117
ApplyContext & ctx_
Definition Transactor.h:112
static base_uint fromVoid(void const *data)
Definition base_uint.h:293
pointer data()
Definition base_uint.h:101
static constexpr std::size_t size()
Definition base_uint.h:499
A type-safe wrap around standard integral types.
T data(T... args)
T distance(T... args)
T memcpy(T... args)
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
std::uint32_t toUInt32(Taxon t)
Definition nft.h:28
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:22
Taxon cipheredTaxon(std::uint32_t tokenSeq, Taxon taxon)
Definition nft.h:64
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.
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)
Determines whether the given expiration time has passed.
Definition View.cpp:34
constexpr FlagValue tfNFTokenMintMaskWithoutMutable
Definition TxFlags.h:387
constexpr FlagValue tfNFTokenMintOldMaskWithMutable
Definition TxFlags.h:393
static bool hasOfferFields(PreflightContext const &ctx)
Number root(Number f, unsigned d)
Definition Number.cpp:958
std::size_t constexpr maxTokenURILength
The maximum length of a URI inside an NFT.
Definition Protocol.h:203
constexpr FlagValue tfNFTokenMintOldMask
Definition TxFlags.h:390
@ temMALFORMED
Definition TER.h:67
@ temBAD_NFTOKEN_TRANSFER_FEE
Definition TER.h:107
bool isTesSuccess(TER x) noexcept
Definition TER.h:651
@ tecINTERNAL
Definition TER.h:291
@ tecEXPIRED
Definition TER.h:295
@ tecINSUFFICIENT_RESERVE
Definition TER.h:288
@ tecMAX_SEQUENCE_REACHED
Definition TER.h:301
@ tecNO_PERMISSION
Definition TER.h:286
@ tecNO_ISSUER
Definition TER.h:280
std::uint16_t constexpr maxTransferFee
The maximum token transfer fee allowed.
Definition Protocol.h:66
static std::uint16_t extractNFTokenFlagsFromTxFlags(std::uint32_t txFlags)
@ tesSUCCESS
Definition TER.h:225
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:57
ReadView const & view
Definition Transactor.h:60
beast::Journal const j
Definition Transactor.h:65
State information when preflighting a tx.
Definition Transactor.h:14