-
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 31 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,60 @@ 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 a NFT before, we must | ||
// initialize sfFirstNFTokenSequence to equal to the current account | ||
// sequence. In general, we must subtract account sequence by | ||
// one, since it is incremented by the transactor beforehand. In | ||
// scenarios of AuthorizedMinting or Tickets, we use the | ||
// account sequence as it is because it has not been incremented. | ||
// | ||
// Whether we subtract the acct sequence by 1 is unimportant in real | ||
// world use case. But it is needed in order to deterministically | ||
// generate the NFTokenSequence for test cases. | ||
if (auto fts = (*root)[~sfFirstNFTokenSequence]; !fts) | ||
{ | ||
auto const accSeq = (*root)[sfSequence]; | ||
|
||
(*root)[sfMintedNFTokens] = nextTokenSeq; | ||
(*root)[sfFirstNFTokenSequence] = | ||
(*root)[~sfNFTokenMinter] == ctx_.tx[sfAccount] || | ||
ctx_.tx.getSeqProxy().isTicket() | ||
? accSeq | ||
: accSeq - 1; | ||
} | ||
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. You have the right behavior, but I don't think the reasoning is correct. I suggest rewriting the comments and code like this:
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. what would be the benefit using using 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. Excellent question. There's no execution benefit. It's a subjective call. Many folks find the It's entirely your call for which would make the code easier for you to read when you maintain it in the future. 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. Thank you! |
||
|
||
auto const mintedNftCnt = (*root)[~sfMintedNFTokens].value_or(0); | ||
shawnxie999 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
(*root)[sfMintedNFTokens] = mintedNftCnt + 1; | ||
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. Integer literals are implicitly signed. A cautious approach to this line would be to add 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. just curious what benefit would i get from explicitly write it as an unsigned int? 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. C++ does implicit type conversions between integers. That's something left over from C. If we know that I don't have the integer conversion rules committed to memory; they are too complicated. Instead I just try to be very conservative when I'm dealing with integers that might overflow. If integer overflow might happen then everything possible needs to be done to make sure everything is unsigned. At least that's the way I approach it. |
||
if ((*root)[sfMintedNFTokens] == 0) | ||
shawnxie999 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return Unexpected(tecMAX_SEQUENCE_REACHED); | ||
|
||
// Get the unique sequence number of this token by | ||
// sfFirstNFTokenSequence + sfMintedNFTokens | ||
auto const offset = (*root)[sfFirstNFTokenSequence]; | ||
auto const tokenSeq = offset + mintedNftCnt; | ||
shawnxie999 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Check for more overflow cases | ||
if (tokenSeq + 1 == 0 || tokenSeq < mintedNftCnt) | ||
shawnxie999 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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.