ERC721Consecutive.sol 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. // SPDX-License-Identifier: MIT
  2. // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/extensions/ERC721Consecutive.sol)
  3. pragma solidity ^0.8.20;
  4. import {ERC721} from "../ERC721.sol";
  5. import {IERC2309} from "../../../interfaces/IERC2309.sol";
  6. import {BitMaps} from "../../../utils/structs/BitMaps.sol";
  7. import {Checkpoints} from "../../../utils/structs/Checkpoints.sol";
  8. /**
  9. * @dev Implementation of the ERC2309 "Consecutive Transfer Extension" as defined in
  10. * https://eips.ethereum.org/EIPS/eip-2309[EIP-2309].
  11. *
  12. * This extension allows the minting of large batches of tokens, during contract construction only. For upgradeable
  13. * contracts this implies that batch minting is only available during proxy deployment, and not in subsequent upgrades.
  14. * These batches are limited to 5000 tokens at a time by default to accommodate off-chain indexers.
  15. *
  16. * Using this extension removes the ability to mint single tokens during contract construction. This ability is
  17. * regained after construction. During construction, only batch minting is allowed.
  18. *
  19. * IMPORTANT: This extension bypasses the hooks {_beforeTokenTransfer} and {_afterTokenTransfer} for tokens minted in
  20. * batch. The hooks will be only called once per batch, so you should take `batchSize` parameter into consideration
  21. * when relying on hooks.
  22. *
  23. * IMPORTANT: When overriding {_afterTokenTransfer}, be careful about call ordering. {ownerOf} may return invalid
  24. * values during the {_afterTokenTransfer} execution if the super call is not called first. To be safe, execute the
  25. * super call before your custom logic.
  26. */
  27. abstract contract ERC721Consecutive is IERC2309, ERC721 {
  28. using BitMaps for BitMaps.BitMap;
  29. using Checkpoints for Checkpoints.Trace160;
  30. Checkpoints.Trace160 private _sequentialOwnership;
  31. BitMaps.BitMap private _sequentialBurn;
  32. /**
  33. * @dev Batch mint is restricted to the constructor.
  34. * Any batch mint not emitting the {IERC721-Transfer} event outside of the constructor
  35. * is non-ERC721 compliant.
  36. */
  37. error ERC721ForbiddenBatchMint();
  38. /**
  39. * @dev Exceeds the max amount of mints per batch.
  40. */
  41. error ERC721ExceededMaxBatchMint(uint256 batchSize, uint256 maxBatch);
  42. /**
  43. * @dev Individual minting is not allowed.
  44. */
  45. error ERC721ForbiddenMint();
  46. /**
  47. * @dev Batch burn is not supported.
  48. */
  49. error ERC721ForbiddenBatchBurn();
  50. /**
  51. * @dev Maximum size of a batch of consecutive tokens. This is designed to limit stress on off-chain indexing
  52. * services that have to record one entry per token, and have protections against "unreasonably large" batches of
  53. * tokens.
  54. *
  55. * NOTE: Overriding the default value of 5000 will not cause on-chain issues, but may result in the asset not being
  56. * correctly supported by off-chain indexing services (including marketplaces).
  57. */
  58. function _maxBatchSize() internal view virtual returns (uint96) {
  59. return 5000;
  60. }
  61. /**
  62. * @dev See {ERC721-_ownerOf}. Override that checks the sequential ownership structure for tokens that have
  63. * been minted as part of a batch, and not yet transferred.
  64. */
  65. function _ownerOf(uint256 tokenId) internal view virtual override returns (address) {
  66. address owner = super._ownerOf(tokenId);
  67. // If token is owned by the core, or beyond consecutive range, return base value
  68. if (owner != address(0) || tokenId > type(uint96).max || tokenId < _firstConsecutiveId()) {
  69. return owner;
  70. }
  71. // Otherwise, check the token was not burned, and fetch ownership from the anchors
  72. // Note: no need for safe cast, we know that tokenId <= type(uint96).max
  73. return _sequentialBurn.get(tokenId) ? address(0) : address(_sequentialOwnership.lowerLookup(uint96(tokenId)));
  74. }
  75. /**
  76. * @dev Mint a batch of tokens of length `batchSize` for `to`. Returns the token id of the first token minted in the
  77. * batch; if `batchSize` is 0, returns the number of consecutive ids minted so far.
  78. *
  79. * Requirements:
  80. *
  81. * - `batchSize` must not be greater than {_maxBatchSize}.
  82. * - The function is called in the constructor of the contract (directly or indirectly).
  83. *
  84. * CAUTION: Does not emit a `Transfer` event. This is ERC721 compliant as long as it is done inside of the
  85. * constructor, which is enforced by this function.
  86. *
  87. * CAUTION: Does not invoke `onERC721Received` on the receiver.
  88. *
  89. * Emits a {IERC2309-ConsecutiveTransfer} event.
  90. */
  91. function _mintConsecutive(address to, uint96 batchSize) internal virtual returns (uint96) {
  92. uint96 next = _nextConsecutiveId();
  93. // minting a batch of size 0 is a no-op
  94. if (batchSize > 0) {
  95. if (address(this).code.length > 0) {
  96. revert ERC721ForbiddenBatchMint();
  97. }
  98. if (to == address(0)) {
  99. revert ERC721InvalidReceiver(address(0));
  100. }
  101. uint256 maxBatchSize = _maxBatchSize();
  102. if (batchSize > maxBatchSize) {
  103. revert ERC721ExceededMaxBatchMint(batchSize, maxBatchSize);
  104. }
  105. // hook before
  106. _beforeTokenTransfer(address(0), to, next, batchSize);
  107. // push an ownership checkpoint & emit event
  108. uint96 last = next + batchSize - 1;
  109. _sequentialOwnership.push(last, uint160(to));
  110. // The invariant required by this function is preserved because the new sequentialOwnership checkpoint
  111. // is attributing ownership of `batchSize` new tokens to account `to`.
  112. __unsafe_increaseBalance(to, batchSize);
  113. emit ConsecutiveTransfer(next, last, address(0), to);
  114. // hook after
  115. _afterTokenTransfer(address(0), to, next, batchSize);
  116. }
  117. return next;
  118. }
  119. /**
  120. * @dev See {ERC721-_mint}. Override version that restricts normal minting to after construction.
  121. *
  122. * WARNING: Using {ERC721Consecutive} prevents using {_mint} during construction in favor of {_mintConsecutive}.
  123. * After construction, {_mintConsecutive} is no longer available and {_mint} becomes available.
  124. */
  125. function _mint(address to, uint256 tokenId) internal virtual override {
  126. if (address(this).code.length == 0) {
  127. revert ERC721ForbiddenMint();
  128. }
  129. super._mint(to, tokenId);
  130. }
  131. /**
  132. * @dev See {ERC721-_afterTokenTransfer}. Burning of tokens that have been sequentially minted must be explicit.
  133. */
  134. function _afterTokenTransfer(
  135. address from,
  136. address to,
  137. uint256 firstTokenId,
  138. uint256 batchSize
  139. ) internal virtual override {
  140. if (
  141. to == address(0) && // if we burn
  142. firstTokenId >= _firstConsecutiveId() &&
  143. firstTokenId < _nextConsecutiveId() &&
  144. !_sequentialBurn.get(firstTokenId)
  145. ) // and the token was never marked as burnt
  146. {
  147. if (batchSize != 1) {
  148. revert ERC721ForbiddenBatchBurn();
  149. }
  150. _sequentialBurn.set(firstTokenId);
  151. }
  152. super._afterTokenTransfer(from, to, firstTokenId, batchSize);
  153. }
  154. /**
  155. * @dev Used to offset the first token id in {_nextConsecutiveId}
  156. */
  157. function _firstConsecutiveId() internal view virtual returns (uint96) {
  158. return 0;
  159. }
  160. /**
  161. * @dev Returns the next tokenId to mint using {_mintConsecutive}. It will return {_firstConsecutiveId}
  162. * if no consecutive tokenId has been minted before.
  163. */
  164. function _nextConsecutiveId() private view returns (uint96) {
  165. (bool exists, uint96 latestId, ) = _sequentialOwnership.latestCheckpoint();
  166. return exists ? latestId + 1 : _firstConsecutiveId();
  167. }
  168. }