-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
fixNFTokenRemint
: prevent NFT re-mint:
#4406
Changes from all commits
e5d61cf
95f644f
60ab256
9561268
51fe860
dc4f146
6211841
fa19aa2
6233a8c
5425a32
a48a437
ce42b74
40c55e9
9c3855b
c95f9e8
1a276a0
31959db
bdaeee2
df7eb5d
0c77813
05602af
99ff97f
d464543
4a26e80
facf8f0
4ef2890
386ffec
53f3238
8747f8e
c4d4487
d04f37f
9bfa3ed
5a174b3
8041edd
f874f0f
fdb7a92
a217f29
ae09383
ac8712e
7d6644f
dacbb52
acf0873
6461c58
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -160,15 +160,66 @@ NFTokenMint::doApply() | |
// Should not happen. Checked in preclaim. | ||
return Unexpected(tecNO_ISSUER); | ||
|
||
// Get the unique sequence number for this token: | ||
std::uint32_t const tokenSeq = (*root)[~sfMintedNFTokens].value_or(0); | ||
if (!ctx_.view().rules().enabled(fixNFTokenRemint)) | ||
{ | ||
std::uint32_t const nextTokenSeq = tokenSeq + 1; | ||
if (nextTokenSeq < tokenSeq) | ||
return Unexpected(tecMAX_SEQUENCE_REACHED); | ||
// Get the unique sequence number for this token: | ||
std::uint32_t const tokenSeq = | ||
(*root)[~sfMintedNFTokens].value_or(0); | ||
{ | ||
std::uint32_t const nextTokenSeq = tokenSeq + 1; | ||
if (nextTokenSeq < tokenSeq) | ||
return Unexpected(tecMAX_SEQUENCE_REACHED); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so you're just checking for int overflow based on ledger seq? that seems unnecessary There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd leave the check as is, as it's basically free (from a computational point of view). |
||
|
||
(*root)[sfMintedNFTokens] = nextTokenSeq; | ||
} | ||
shawnxie999 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ctx_.view().update(root); | ||
return tokenSeq; | ||
} | ||
|
||
// With fixNFTokenRemint amendment enabled: | ||
// | ||
// If the issuer hasn't minted an NFToken before we must add a | ||
// FirstNFTokenSequence field to the issuer's AccountRoot. The | ||
// value of the FirstNFTokenSequence must equal the issuer's | ||
// current account sequence. | ||
// | ||
// There are three situations: | ||
// o If the first token is being minted by the issuer and | ||
// * If the transaction consumes a Sequence number, then the | ||
// Sequence has been pre-incremented by the time we get here in | ||
// doApply. We must decrement the value in the Sequence field. | ||
// * Otherwise the transaction uses a Ticket so the Sequence has | ||
// not been pre-incremented. We use the Sequence value as is. | ||
// o The first token is being minted by an authorized minter. In | ||
// this case the issuer's Sequence field has been left untouched. | ||
// We use the issuer's Sequence value as is. | ||
shawnxie999 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!root->isFieldPresent(sfFirstNFTokenSequence)) | ||
{ | ||
std::uint32_t const acctSeq = root->at(sfSequence); | ||
|
||
(*root)[sfMintedNFTokens] = nextTokenSeq; | ||
root->at(sfFirstNFTokenSequence) = | ||
ctx_.tx.isFieldPresent(sfIssuer) || | ||
ctx_.tx.getSeqProxy().isTicket() | ||
? acctSeq | ||
: acctSeq - 1; | ||
} | ||
|
||
std::uint32_t const mintedNftCnt = | ||
(*root)[~sfMintedNFTokens].value_or(0u); | ||
|
||
(*root)[sfMintedNFTokens] = mintedNftCnt + 1u; | ||
if ((*root)[sfMintedNFTokens] == 0u) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. doesn't need to change - but i don't think you need the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is a conservative approach to make sure things are correctly typed proposed by @scottschurr There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ledhed2222, what you describe is not always the case. There are situations where operations on a combination of unsigned and signed integers produce a signed value. See "Usual Arithmetic Conversions": https://en.cppreference.com/w/cpp/language/usual_arithmetic_conversions#Integer_conversion_rank You'll notice that the rules changed with C++11 and again with C++23. What the rules seem to be pretty good at holding onto is that if both arguments are unsigned then the result will be unsigned. Since the literals I did not go to the effort of determining exactly what conversions the compiler would make -- I'm not that smart. I just applied a heuristic so I'm increasing the likelihood the compiler will do what I want. |
||
return Unexpected(tecMAX_SEQUENCE_REACHED); | ||
|
||
// Get the unique sequence number of this token by | ||
// sfFirstNFTokenSequence + sfMintedNFTokens | ||
std::uint32_t const offset = (*root)[sfFirstNFTokenSequence]; | ||
std::uint32_t const tokenSeq = offset + mintedNftCnt; | ||
|
||
// Check for more overflow cases | ||
if (tokenSeq + 1u == 0u || tokenSeq < offset) | ||
return Unexpected(tecMAX_SEQUENCE_REACHED); | ||
|
||
ctx_.view().update(root); | ||
return tokenSeq; | ||
}(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,6 +55,7 @@ LedgerFormats::LedgerFormats() | |
{sfNFTokenMinter, soeOPTIONAL}, | ||
{sfMintedNFTokens, soeDEFAULT}, | ||
{sfBurnedNFTokens, soeDEFAULT}, | ||
{sfFirstNFTokenSequence, soeOPTIONAL}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note - this will require change to the SDKs I believe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would you confirm this? doesn't block this PR but when a TX modifies this value and one of the libraries is trying to parse the metadata from that TX I think it will raise an exception |
||
}, | ||
commonFields); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Imagine someone issuing a million "Silly Billy" NFTs, with the first minted in ledger 77,777,777.
This check will prevent the issuer from deleting the account until, at the earliest, ledger 78,778,032; that's not gonna happen for around 4 million seconds, or around a month and a half.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, unfortunately this check needs to be in. Without it, the scenario below produces duplicate NFTID:
FirstNFTokenSequence
+MintedNFTokens
)FirstNFTokenSequence
initialized to her account sequence (258
), andMintedNFTokens
to0
. This newly minted NFT would have a sequence number of258
, which is a duplicate of what she issued through authorized minting before she deleted her account.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand. My point was that this kind of test means it's hard for someone to know when their account can be deleted and, depending on the number of NFTs they issue. For some accounts (those which don't have
FirstNFTokenSequence
set) the answer is "immediately" and for others it's "well, sometime in the future."There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I don't think this will be a common issue, since this restriction is only applicable for authorized minting situations.
If the issuer has never issued an NFT through authorized minting, then,
FirstNFTokenSequence + MintedNFTokens <= AccountRoot Sequence
will always be true, which means the AccountDelete amendment (ledger > Account Sequence + 256) already contains the new restriction.