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