@@ -30,6 +30,9 @@ interface ERC721A__IERC721ReceiverUpgradeable {
30
30
* Token IDs are minted in sequential order (e.g. 0, 1, 2, 3, ...)
31
31
* starting from `_startTokenId()`.
32
32
*
33
+ * The `_sequentialUpTo()` function can be overriden to enable spot mints
34
+ * (i.e. non-consecutive mints) for `tokenId`s greater than `_sequentialUpTo()`.
35
+ *
33
36
* Assumptions:
34
37
*
35
38
* - An owner cannot have more than 2**64 - 1 (max value of uint64) of supply.
@@ -101,20 +104,37 @@ contract ERC721AUpgradeable is ERC721A__Initializable, IERC721AUpgradeable {
101
104
ERC721AStorage .layout ()._name = name_;
102
105
ERC721AStorage .layout ()._symbol = symbol_;
103
106
ERC721AStorage .layout ()._currentIndex = _startTokenId ();
107
+
108
+ if (_sequentialUpTo () < _startTokenId ()) _revert (SequentialUpToTooSmall.selector );
104
109
}
105
110
106
111
// =============================================================
107
112
// TOKEN COUNTING OPERATIONS
108
113
// =============================================================
109
114
110
115
/**
111
- * @dev Returns the starting token ID.
112
- * To change the starting token ID, please override this function.
116
+ * @dev Returns the starting token ID for sequential mints.
117
+ *
118
+ * Override this function to change the starting token ID for sequential mints.
119
+ *
120
+ * Note: The value returned must never change after any tokens have been minted.
113
121
*/
114
122
function _startTokenId () internal view virtual returns (uint256 ) {
115
123
return 0 ;
116
124
}
117
125
126
+ /**
127
+ * @dev Returns the maximum token ID (inclusive) for sequential mints.
128
+ *
129
+ * Override this function to return a value less than 2**256 - 1,
130
+ * but greater than `_startTokenId()`, to enable spot (non-sequential) mints.
131
+ *
132
+ * Note: The value returned must never change after any tokens have been minted.
133
+ */
134
+ function _sequentialUpTo () internal view virtual returns (uint256 ) {
135
+ return type (uint256 ).max;
136
+ }
137
+
118
138
/**
119
139
* @dev Returns the next token ID to be minted.
120
140
*/
@@ -127,22 +147,26 @@ contract ERC721AUpgradeable is ERC721A__Initializable, IERC721AUpgradeable {
127
147
* Burned tokens will reduce the count.
128
148
* To get the total number of tokens minted, please see {_totalMinted}.
129
149
*/
130
- function totalSupply () public view virtual override returns (uint256 ) {
131
- // Counter underflow is impossible as _burnCounter cannot be incremented
132
- // more than `_currentIndex - _startTokenId()` times.
150
+ function totalSupply () public view virtual override returns (uint256 result ) {
151
+ // Counter underflow is impossible as ` _burnCounter` cannot be incremented
152
+ // more than `_currentIndex + _spotMinted - _startTokenId()` times.
133
153
unchecked {
134
- return ERC721AStorage .layout ()._currentIndex - ERC721AStorage .layout ()._burnCounter - _startTokenId ();
154
+ // With spot minting, the intermediate `result` can be temporarily negative,
155
+ // and the computation must be unchecked.
156
+ result = ERC721AStorage .layout ()._currentIndex - ERC721AStorage .layout ()._burnCounter - _startTokenId ();
157
+ if (_sequentialUpTo () != type (uint256 ).max) result += ERC721AStorage .layout ()._spotMinted;
135
158
}
136
159
}
137
160
138
161
/**
139
162
* @dev Returns the total amount of tokens minted in the contract.
140
163
*/
141
- function _totalMinted () internal view virtual returns (uint256 ) {
164
+ function _totalMinted () internal view virtual returns (uint256 result ) {
142
165
// Counter underflow is impossible as `_currentIndex` does not decrement,
143
166
// and it is initialized to `_startTokenId()`.
144
167
unchecked {
145
- return ERC721AStorage .layout ()._currentIndex - _startTokenId ();
168
+ result = ERC721AStorage .layout ()._currentIndex - _startTokenId ();
169
+ if (_sequentialUpTo () != type (uint256 ).max) result += ERC721AStorage .layout ()._spotMinted;
146
170
}
147
171
}
148
172
@@ -153,6 +177,13 @@ contract ERC721AUpgradeable is ERC721A__Initializable, IERC721AUpgradeable {
153
177
return ERC721AStorage .layout ()._burnCounter;
154
178
}
155
179
180
+ /**
181
+ * @dev Returns the total number of tokens that are spot-minted.
182
+ */
183
+ function _totalSpotMinted () internal view virtual returns (uint256 ) {
184
+ return ERC721AStorage .layout ()._spotMinted;
185
+ }
186
+
156
187
// =============================================================
157
188
// ADDRESS DATA OPERATIONS
158
189
// =============================================================
@@ -311,11 +342,17 @@ contract ERC721AUpgradeable is ERC721A__Initializable, IERC721AUpgradeable {
311
342
}
312
343
313
344
/**
314
- * Returns the packed ownership data of `tokenId`.
345
+ * @dev Returns the packed ownership data of `tokenId`.
315
346
*/
316
347
function _packedOwnershipOf (uint256 tokenId ) private view returns (uint256 packed ) {
317
348
if (_startTokenId () <= tokenId) {
318
349
packed = ERC721AStorage .layout ()._packedOwnerships[tokenId];
350
+
351
+ if (tokenId > _sequentialUpTo ()) {
352
+ if (_packedOwnershipExists (packed)) return packed;
353
+ _revert (OwnerQueryForNonexistentToken.selector );
354
+ }
355
+
319
356
// If the data at the starting slot does not exist, start the scan.
320
357
if (packed == 0 ) {
321
358
if (tokenId >= ERC721AStorage .layout ()._currentIndex) _revert (OwnerQueryForNonexistentToken.selector );
@@ -444,6 +481,9 @@ contract ERC721AUpgradeable is ERC721A__Initializable, IERC721AUpgradeable {
444
481
*/
445
482
function _exists (uint256 tokenId ) internal view virtual returns (bool result ) {
446
483
if (_startTokenId () <= tokenId) {
484
+ if (tokenId > _sequentialUpTo ())
485
+ return _packedOwnershipExists (ERC721AStorage .layout ()._packedOwnerships[tokenId]);
486
+
447
487
if (tokenId < ERC721AStorage .layout ()._currentIndex) {
448
488
uint256 packed;
449
489
while ((packed = ERC721AStorage .layout ()._packedOwnerships[tokenId]) == 0 ) -- tokenId;
@@ -452,6 +492,17 @@ contract ERC721AUpgradeable is ERC721A__Initializable, IERC721AUpgradeable {
452
492
}
453
493
}
454
494
495
+ /**
496
+ * @dev Returns whether `packed` represents a token that exists.
497
+ */
498
+ function _packedOwnershipExists (uint256 packed ) private pure returns (bool result ) {
499
+ assembly {
500
+ // The following is equivalent to `owner != address(0) && burned == false`.
501
+ // Symbolically tested.
502
+ result := gt (and (packed, _BITMASK_ADDRESS), and (packed, _BITMASK_BURNED))
503
+ }
504
+ }
505
+
455
506
/**
456
507
* @dev Returns whether `msgSender` is equal to `approvedAddress` or `owner`.
457
508
*/
@@ -745,6 +796,8 @@ contract ERC721AUpgradeable is ERC721A__Initializable, IERC721AUpgradeable {
745
796
uint256 end = startTokenId + quantity;
746
797
uint256 tokenId = startTokenId;
747
798
799
+ if (end - 1 > _sequentialUpTo ()) _revert (SequentialMintExceedsLimit.selector );
800
+
748
801
do {
749
802
assembly {
750
803
// Emit the `Transfer` event.
@@ -814,6 +867,8 @@ contract ERC721AUpgradeable is ERC721A__Initializable, IERC721AUpgradeable {
814
867
_nextInitializedFlag (quantity) | _nextExtraData (address (0 ), to, 0 )
815
868
);
816
869
870
+ if (startTokenId + quantity - 1 > _sequentialUpTo ()) _revert (SequentialMintExceedsLimit.selector );
871
+
817
872
emit ConsecutiveTransfer (startTokenId, startTokenId + quantity - 1 , address (0 ), to);
818
873
819
874
ERC721AStorage .layout ()._currentIndex = startTokenId + quantity;
@@ -850,8 +905,9 @@ contract ERC721AUpgradeable is ERC721A__Initializable, IERC721AUpgradeable {
850
905
_revert (TransferToNonERC721ReceiverImplementer.selector );
851
906
}
852
907
} while (index < end);
853
- // Reentrancy protection.
854
- if (ERC721AStorage .layout ()._currentIndex != end) _revert (bytes4 (0 ));
908
+ // This prevents reentrancy to `_safeMint`.
909
+ // It does not prevent reentrancy to `_safeMintSpot`.
910
+ if (ERC721AStorage .layout ()._currentIndex != end) revert ();
855
911
}
856
912
}
857
913
}
@@ -863,6 +919,112 @@ contract ERC721AUpgradeable is ERC721A__Initializable, IERC721AUpgradeable {
863
919
_safeMint (to, quantity, '' );
864
920
}
865
921
922
+ /**
923
+ * @dev Mints a single token at `tokenId`.
924
+ *
925
+ * Note: A spot-minted `tokenId` that has been burned can be re-minted again.
926
+ *
927
+ * Requirements:
928
+ *
929
+ * - `to` cannot be the zero address.
930
+ * - `tokenId` must be greater than `_sequentialUpTo()`.
931
+ * - `tokenId` must not exist.
932
+ *
933
+ * Emits a {Transfer} event for each mint.
934
+ */
935
+ function _mintSpot (address to , uint256 tokenId ) internal virtual {
936
+ if (tokenId <= _sequentialUpTo ()) _revert (SpotMintTokenIdTooSmall.selector );
937
+ uint256 prevOwnershipPacked = ERC721AStorage .layout ()._packedOwnerships[tokenId];
938
+ if (_packedOwnershipExists (prevOwnershipPacked)) _revert (TokenAlreadyExists.selector );
939
+
940
+ _beforeTokenTransfers (address (0 ), to, tokenId, 1 );
941
+
942
+ // Overflows are incredibly unrealistic.
943
+ // The `numberMinted` for `to` is incremented by 1, and has a max limit of 2**64 - 1.
944
+ // `_spotMinted` is incremented by 1, and has a max limit of 2**256 - 1.
945
+ unchecked {
946
+ // Updates:
947
+ // - `address` to the owner.
948
+ // - `startTimestamp` to the timestamp of minting.
949
+ // - `burned` to `false`.
950
+ // - `nextInitialized` to `true` (as `quantity == 1`).
951
+ ERC721AStorage .layout ()._packedOwnerships[tokenId] = _packOwnershipData (
952
+ to,
953
+ _nextInitializedFlag (1 ) | _nextExtraData (address (0 ), to, prevOwnershipPacked)
954
+ );
955
+
956
+ // Updates:
957
+ // - `balance += 1`.
958
+ // - `numberMinted += 1`.
959
+ //
960
+ // We can directly add to the `balance` and `numberMinted`.
961
+ ERC721AStorage .layout ()._packedAddressData[to] += (1 << _BITPOS_NUMBER_MINTED) | 1 ;
962
+
963
+ // Mask `to` to the lower 160 bits, in case the upper bits somehow aren't clean.
964
+ uint256 toMasked = uint256 (uint160 (to)) & _BITMASK_ADDRESS;
965
+
966
+ if (toMasked == 0 ) _revert (MintToZeroAddress.selector );
967
+
968
+ assembly {
969
+ // Emit the `Transfer` event.
970
+ log4 (
971
+ 0 , // Start of data (0, since no data).
972
+ 0 , // End of data (0, since no data).
973
+ _TRANSFER_EVENT_SIGNATURE, // Signature.
974
+ 0 , // `address(0)`.
975
+ toMasked, // `to`.
976
+ tokenId // `tokenId`.
977
+ )
978
+ }
979
+
980
+ ++ ERC721AStorage .layout ()._spotMinted;
981
+ }
982
+
983
+ _afterTokenTransfers (address (0 ), to, tokenId, 1 );
984
+ }
985
+
986
+ /**
987
+ * @dev Safely mints a single token at `tokenId`.
988
+ *
989
+ * Note: A spot-minted `tokenId` that has been burned can be re-minted again.
990
+ *
991
+ * Requirements:
992
+ *
993
+ * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}.
994
+ * - `tokenId` must be greater than `_sequentialUpTo()`.
995
+ * - `tokenId` must not exist.
996
+ *
997
+ * See {_mintSpot}.
998
+ *
999
+ * Emits a {Transfer} event.
1000
+ */
1001
+ function _safeMintSpot (
1002
+ address to ,
1003
+ uint256 tokenId ,
1004
+ bytes memory _data
1005
+ ) internal virtual {
1006
+ _mintSpot (to, tokenId);
1007
+
1008
+ unchecked {
1009
+ if (to.code.length != 0 ) {
1010
+ uint256 currentSpotMinted = ERC721AStorage .layout ()._spotMinted;
1011
+ if (! _checkContractOnERC721Received (address (0 ), to, tokenId, _data)) {
1012
+ _revert (TransferToNonERC721ReceiverImplementer.selector );
1013
+ }
1014
+ // This prevents reentrancy to `_safeMintSpot`.
1015
+ // It does not prevent reentrancy to `_safeMint`.
1016
+ if (ERC721AStorage .layout ()._spotMinted != currentSpotMinted) revert ();
1017
+ }
1018
+ }
1019
+ }
1020
+
1021
+ /**
1022
+ * @dev Equivalent to `_safeMintSpot(to, tokenId, '')`.
1023
+ */
1024
+ function _safeMintSpot (address to , uint256 tokenId ) internal virtual {
1025
+ _safeMintSpot (to, tokenId, '' );
1026
+ }
1027
+
866
1028
// =============================================================
867
1029
// APPROVAL OPERATIONS
868
1030
// =============================================================
@@ -986,7 +1148,7 @@ contract ERC721AUpgradeable is ERC721A__Initializable, IERC721AUpgradeable {
986
1148
emit Transfer (from, address (0 ), tokenId);
987
1149
_afterTokenTransfers (from, address (0 ), tokenId, 1 );
988
1150
989
- // Overflow not possible, as _burnCounter cannot be exceed _currentIndex times.
1151
+ // Overflow not possible, as ` _burnCounter` cannot be exceed ` _currentIndex + _spotMinted` times.
990
1152
unchecked {
991
1153
ERC721AStorage .layout ()._burnCounter++ ;
992
1154
}
0 commit comments