rippled
Loading...
Searching...
No Matches
NFTokenMint.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2021 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/tx/detail/NFTokenMint.h>
21
22#include <xrpl/basics/Expected.h>
23#include <xrpl/ledger/View.h>
24#include <xrpl/protocol/Feature.h>
25#include <xrpl/protocol/InnerObjectFormats.h>
26#include <xrpl/protocol/Rate.h>
27#include <xrpl/protocol/TxFlags.h>
28
29#include <boost/endian/conversion.hpp>
30
31#include <array>
32
33namespace ripple {
34
35static std::uint16_t
37{
38 return static_cast<std::uint16_t>(txFlags & 0x0000FFFF);
39}
40
43{
44 if (!ctx.rules.enabled(featureNonFungibleTokensV1))
45 return temDISABLED;
46
47 bool const hasOfferFields = ctx.tx.isFieldPresent(sfAmount) ||
48 ctx.tx.isFieldPresent(sfDestination) ||
49 ctx.tx.isFieldPresent(sfExpiration);
50
51 if (!ctx.rules.enabled(featureNFTokenMintOffer) && hasOfferFields)
52 return temDISABLED;
53
54 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
55 return ret;
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 =
71 ctx.rules.enabled(fixRemoveNFTokenAutoTrustLine)
72 // if featureDynamicNFT enabled then new flag allowing mutable URI
73 // available
74 ? ctx.rules.enabled(featureDynamicNFT) ? tfNFTokenMintMaskWithMutable
76 : ctx.rules.enabled(featureDynamicNFT) ? tfNFTokenMintOldMaskWithMutable
78
79 if (ctx.tx.getFlags() & NFTokenMintMask)
80 return temINVALID_FLAG;
81
82 if (auto const f = ctx.tx[~sfTransferFee])
83 {
84 if (f > maxTransferFee)
86
87 // If a non-zero TransferFee is set then the tfTransferable flag
88 // must also be set.
89 if (f > 0u && !ctx.tx.isFlag(tfTransferable))
90 return temMALFORMED;
91 }
92
93 // An issuer must only be set if the tx is executed by the minter
94 if (auto iss = ctx.tx[~sfIssuer]; iss == ctx.tx[sfAccount])
95 return temMALFORMED;
96
97 if (auto uri = ctx.tx[~sfURI])
98 {
99 if (uri->length() == 0 || uri->length() > maxTokenURILength)
100 return temMALFORMED;
101 }
102
103 if (hasOfferFields)
104 {
105 // The Amount field must be present if either the Destination or
106 // Expiration fields are present.
107 if (!ctx.tx.isFieldPresent(sfAmount))
108 return temMALFORMED;
109
110 // Rely on the common code shared with NFTokenCreateOffer to
111 // do the validation. We pass tfSellNFToken as the transaction flags
112 // because a Mint is only allowed to create a sell offer.
114 ctx.tx[sfAccount],
115 ctx.tx[sfAmount],
116 ctx.tx[~sfDestination],
117 ctx.tx[~sfExpiration],
119 ctx.rules);
120 !isTesSuccess(notTec))
121 {
122 return notTec;
123 }
124 }
125
126 return preflight2(ctx);
127}
128
131 std::uint16_t flags,
132 std::uint16_t fee,
133 AccountID const& issuer,
134 nft::Taxon taxon,
135 std::uint32_t tokenSeq)
136{
137 // An issuer may issue several NFTs with the same taxon; to ensure that NFTs
138 // are spread across multiple pages we lightly mix the taxon up by using the
139 // sequence (which is not under the issuer's direct control) as the seed for
140 // a simple linear congruential generator. cipheredTaxon() does this work.
141 taxon = nft::cipheredTaxon(tokenSeq, taxon);
142
143 // The values are packed inside a 32-byte buffer, so we need to make sure
144 // that the endianess is fixed.
145 flags = boost::endian::native_to_big(flags);
146 fee = boost::endian::native_to_big(fee);
147 taxon = nft::toTaxon(boost::endian::native_to_big(nft::toUInt32(taxon)));
148 tokenSeq = boost::endian::native_to_big(tokenSeq);
149
151
152 auto ptr = buf.data();
153
154 // This code is awkward but the idea is to pack these values into a single
155 // 256-bit value that uniquely identifies this NFT.
156 std::memcpy(ptr, &flags, sizeof(flags));
157 ptr += sizeof(flags);
158
159 std::memcpy(ptr, &fee, sizeof(fee));
160 ptr += sizeof(fee);
161
162 std::memcpy(ptr, issuer.data(), issuer.size());
163 ptr += issuer.size();
164
165 std::memcpy(ptr, &taxon, sizeof(taxon));
166 ptr += sizeof(taxon);
167
168 std::memcpy(ptr, &tokenSeq, sizeof(tokenSeq));
169 ptr += sizeof(tokenSeq);
170 XRPL_ASSERT(
171 std::distance(buf.data(), ptr) == buf.size(),
172 "ripple::NFTokenMint::createNFTokenID : data size matches the buffer");
173
174 return uint256::fromVoid(buf.data());
175}
176
177TER
179{
180 // The issuer of the NFT may or may not be the account executing this
181 // transaction. Check that and verify that this is allowed:
182 if (auto issuer = ctx.tx[~sfIssuer])
183 {
184 auto const sle = ctx.view.read(keylet::account(*issuer));
185
186 if (!sle)
187 return tecNO_ISSUER;
188
189 if (auto const minter = (*sle)[~sfNFTokenMinter];
190 minter != ctx.tx[sfAccount])
191 return tecNO_PERMISSION;
192 }
193
194 if (ctx.tx.isFieldPresent(sfAmount))
195 {
196 // The Amount field says create an offer for the minted token.
197 if (hasExpired(ctx.view, ctx.tx[~sfExpiration]))
198 return tecEXPIRED;
199
200 // Rely on the common code shared with NFTokenCreateOffer to
201 // do the validation. We pass tfSellNFToken as the transaction flags
202 // because a Mint is only allowed to create a sell offer.
203 if (TER const ter = nft::tokenOfferCreatePreclaim(
204 ctx.view,
205 ctx.tx[sfAccount],
206 ctx.tx[~sfIssuer].value_or(ctx.tx[sfAccount]),
207 ctx.tx[sfAmount],
208 ctx.tx[~sfDestination],
210 ctx.tx[~sfTransferFee].value_or(0),
211 ctx.j);
212 !isTesSuccess(ter))
213 return ter;
214 }
215 return tesSUCCESS;
216}
217
218TER
220{
221 auto const issuer = ctx_.tx[~sfIssuer].value_or(account_);
222
223 auto const tokenSeq = [this, &issuer]() -> Expected<std::uint32_t, TER> {
224 auto const root = view().peek(keylet::account(issuer));
225 if (root == nullptr)
226 // Should not happen. Checked in preclaim.
227 return Unexpected(tecNO_ISSUER);
228
229 if (!ctx_.view().rules().enabled(fixNFTokenRemint))
230 {
231 // Get the unique sequence number for this token:
232 std::uint32_t const tokenSeq =
233 (*root)[~sfMintedNFTokens].value_or(0);
234 {
235 std::uint32_t const nextTokenSeq = tokenSeq + 1;
236 if (nextTokenSeq < tokenSeq)
238
239 (*root)[sfMintedNFTokens] = nextTokenSeq;
240 }
241 ctx_.view().update(root);
242 return tokenSeq;
243 }
244
245 // With fixNFTokenRemint amendment enabled:
246 //
247 // If the issuer hasn't minted an NFToken before we must add a
248 // FirstNFTokenSequence field to the issuer's AccountRoot. The
249 // value of the FirstNFTokenSequence must equal the issuer's
250 // current account sequence.
251 //
252 // There are three situations:
253 // o If the first token is being minted by the issuer and
254 // * If the transaction consumes a Sequence number, then the
255 // Sequence has been pre-incremented by the time we get here in
256 // doApply. We must decrement the value in the Sequence field.
257 // * Otherwise the transaction uses a Ticket so the Sequence has
258 // not been pre-incremented. We use the Sequence value as is.
259 // o The first token is being minted by an authorized minter. In
260 // this case the issuer's Sequence field has been left untouched.
261 // We use the issuer's Sequence value as is.
262 if (!root->isFieldPresent(sfFirstNFTokenSequence))
263 {
264 std::uint32_t const acctSeq = root->at(sfSequence);
265
266 root->at(sfFirstNFTokenSequence) =
267 ctx_.tx.isFieldPresent(sfIssuer) ||
269 ? acctSeq
270 : acctSeq - 1;
271 }
272
273 std::uint32_t const mintedNftCnt =
274 (*root)[~sfMintedNFTokens].value_or(0u);
275
276 (*root)[sfMintedNFTokens] = mintedNftCnt + 1u;
277 if ((*root)[sfMintedNFTokens] == 0u)
279
280 // Get the unique sequence number of this token by
281 // sfFirstNFTokenSequence + sfMintedNFTokens
282 std::uint32_t const offset = (*root)[sfFirstNFTokenSequence];
283 std::uint32_t const tokenSeq = offset + mintedNftCnt;
284
285 // Check for more overflow cases
286 if (tokenSeq + 1u == 0u || tokenSeq < offset)
288
289 ctx_.view().update(root);
290 return tokenSeq;
291 }();
292
293 if (!tokenSeq.has_value())
294 return (tokenSeq.error());
295
296 std::uint32_t const ownerCountBefore =
297 view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
298
299 // Assemble the new NFToken.
300 SOTemplate const* nfTokenTemplate =
302
303 if (nfTokenTemplate == nullptr)
304 // Should never happen.
305 return tecINTERNAL;
306
307 auto const nftokenID = createNFTokenID(
309 ctx_.tx[~sfTransferFee].value_or(0),
310 issuer,
311 nft::toTaxon(ctx_.tx[sfNFTokenTaxon]),
312 tokenSeq.value());
313
314 STObject newToken(
315 *nfTokenTemplate, sfNFToken, [this, &nftokenID](STObject& object) {
316 object.setFieldH256(sfNFTokenID, nftokenID);
317
318 if (auto const uri = ctx_.tx[~sfURI])
319 object.setFieldVL(sfURI, *uri);
320 });
321
322 if (TER const ret =
323 nft::insertToken(ctx_.view(), account_, std::move(newToken));
324 ret != tesSUCCESS)
325 return ret;
326
327 if (ctx_.tx.isFieldPresent(sfAmount))
328 {
329 // Rely on the common code shared with NFTokenCreateOffer to create
330 // the offer. We pass tfSellNFToken as the transaction flags
331 // because a Mint is only allowed to create a sell offer.
332 if (TER const ter = nft::tokenOfferCreateApply(
333 view(),
334 ctx_.tx[sfAccount],
335 ctx_.tx[sfAmount],
336 ctx_.tx[~sfDestination],
337 ctx_.tx[~sfExpiration],
339 nftokenID,
341 j_);
342 !isTesSuccess(ter))
343 return ter;
344 }
345
346 // Only check the reserve if the owner count actually changed. This
347 // allows NFTs to be added to the page (and burn fees) without
348 // requiring the reserve to be met each time. The reserve is
349 // only managed when a new NFT page or sell offer is added.
350 if (auto const ownerCountAfter =
351 view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
352 ownerCountAfter > ownerCountBefore)
353 {
354 if (auto const reserve = view().fees().accountReserve(ownerCountAfter);
355 mPriorBalance < reserve)
357 }
358 return tesSUCCESS;
359}
360
361} // namespace ripple
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 NotTEC preflight(PreflightContext const &ctx)
static uint256 createNFTokenID(std::uint16_t flags, std::uint16_t fee, AccountID const &issuer, nft::Taxon taxon, std::uint32_t tokenSeq)
static TER preclaim(PreclaimContext const &ctx)
TER doApply() override
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:130
Defines the fields and their attributes within a STObject.
Definition SOTemplate.h:113
bool isFlag(std::uint32_t) const
Definition STObject.cpp:531
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:484
std::uint32_t getFlags() const
Definition STObject.cpp:537
SeqProxy getSeqProxy() const
Definition STTx.cpp:216
constexpr bool isTicket() const
Definition SeqProxy.h:94
AccountID const account_
Definition Transactor.h:145
ApplyView & view()
Definition Transactor.h:161
beast::Journal const j_
Definition Transactor.h:143
XRPAmount mPriorBalance
Definition Transactor.h:146
ApplyContext & ctx_
Definition Transactor.h:141
static base_uint fromVoid(void const *data)
Definition base_uint.h:319
static constexpr std::size_t size()
Definition base_uint.h:526
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:184
std::uint32_t toUInt32(Taxon t)
Definition nft.h:48
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::uint32_t txFlags)
Preflight checks shared by NFTokenCreateOffer and NFTokenMint.
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)
doApply implementation shared by NFTokenCreateOffer and NFTokenMint
Taxon cipheredTaxon(std::uint32_t tokenSeq, Taxon taxon)
Definition nft.h:84
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::uint32_t txFlags)
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:42
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
constexpr std::uint32_t const tfNFTokenMintOldMaskWithMutable
Definition TxFlags.h:223
constexpr std::uint32_t const tfNFTokenMintMaskWithMutable
Definition TxFlags.h:226
constexpr std::uint32_t const tfNFTokenMintOldMask
Definition TxFlags.h:219
std::uint16_t constexpr maxTransferFee
The maximum token transfer fee allowed.
Definition Protocol.h:82
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:173
std::size_t constexpr maxTokenURILength
The maximum length of a URI inside an NFT.
Definition Protocol.h:85
@ tecNO_ISSUER
Definition TER.h:299
@ tecINTERNAL
Definition TER.h:310
@ tecNO_PERMISSION
Definition TER.h:305
@ tecMAX_SEQUENCE_REACHED
Definition TER.h:320
@ tecINSUFFICIENT_RESERVE
Definition TER.h:307
@ tecEXPIRED
Definition TER.h:314
@ tesSUCCESS
Definition TER.h:244
bool isTesSuccess(TER x) noexcept
Definition TER.h:674
Number root(Number f, unsigned d)
Definition Number.cpp:636
static std::uint16_t extractNFTokenFlagsFromTxFlags(std::uint32_t txFlags)
constexpr std::uint32_t const tfTransferable
Definition TxFlags.h:142
constexpr std::uint32_t const tfNFTokenMintMask
Definition TxFlags.h:216
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:605
@ temMALFORMED
Definition TER.h:87
@ temINVALID_FLAG
Definition TER.h:111
@ temDISABLED
Definition TER.h:114
@ temBAD_NFTOKEN_TRANSFER_FEE
Definition TER.h:127
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:80
ReadView const & view
Definition Transactor.h:83
beast::Journal const j
Definition Transactor.h:88
State information when preflighting a tx.
Definition Transactor.h:35