ERC721Consecutive.sol 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. // SPDX-License-Identifier: MIT
  2. pragma solidity ^0.8.0;
  3. import "../ERC721.sol";
  4. import "../../../interfaces/IERC2309.sol";
  5. import "../../../utils/Checkpoints.sol";
  6. import "../../../utils/structs/BitMaps.sol";
  7. /**
  8. * @dev Implementation of the ERC2309 "Consecutive Transfer Extension" as defined in
  9. * https://eips.ethereum.org/EIPS/eip-2309[EIP-2309].
  10. *
  11. * This extension allows the minting of large batches of tokens, during contract construction only. For upgradeable
  12. * contracts this implies that batch minting is only available during proxy deployment, and not in subsequent upgrades.
  13. * These batches are limited to 5000 tokens at a time by default to accommodate off-chain indexers.
  14. *
  15. * Using this extension removes the ability to mint single tokens during contract construction. This ability is
  16. * regained after construction. During construction, only batch minting is allowed.
  17. *
  18. * IMPORTANT: This extension bypasses the hooks {_beforeTokenTransfer} and {_afterTokenTransfer} for tokens minted in
  19. * batch. When using this extension, you should consider the {_beforeConsecutiveTokenTransfer} and
  20. * {_afterConsecutiveTokenTransfer} hooks in addition to {_beforeTokenTransfer} and {_afterTokenTransfer}.
  21. *
  22. * IMPORTANT: When overriding {_afterTokenTransfer}, be careful about call ordering. {ownerOf} may return invalid
  23. * values during the {_afterTokenTransfer} execution if the super call is not called first. To be safe, execute the
  24. * super call before your custom logic.
  25. *
  26. * _Available since v4.8._
  27. */
  28. abstract contract ERC721Consecutive is IERC2309, ERC721 {
  29. using BitMaps for BitMaps.BitMap;
  30. using Checkpoints for Checkpoints.Trace160;
  31. Checkpoints.Trace160 private _sequentialOwnership;
  32. BitMaps.BitMap private _sequentialBurn;
  33. /**
  34. * @dev Maximum size of a batch of consecutive tokens. This is designed to limit stress on off-chain indexing
  35. * services that have to record one entry per token, and have protections against "unreasonably large" batches of
  36. * tokens.
  37. *
  38. * NOTE: Overriding the default value of 5000 will not cause on-chain issues, but may result in the asset not being
  39. * correctly supported by off-chain indexing services (including marketplaces).
  40. */
  41. function _maxBatchSize() internal view virtual returns (uint96) {
  42. return 5000;
  43. }
  44. /**
  45. * @dev See {ERC721-_ownerOf}. Override that checks the sequential ownership structure for tokens that have
  46. * been minted as part of a batch, and not yet transferred.
  47. */
  48. function _ownerOf(uint256 tokenId) internal view virtual override returns (address) {
  49. address owner = super._ownerOf(tokenId);
  50. // If token is owned by the core, or beyond consecutive range, return base value
  51. if (owner != address(0) || tokenId > type(uint96).max) {
  52. return owner;
  53. }
  54. // Otherwise, check the token was not burned, and fetch ownership from the anchors
  55. // Note: no need for safe cast, we know that tokenId <= type(uint96).max
  56. return _sequentialBurn.get(tokenId) ? address(0) : address(_sequentialOwnership.lowerLookup(uint96(tokenId)));
  57. }
  58. /**
  59. * @dev Mint a batch of tokens of length `batchSize` for `to`. Returns the token id of the first token minted in the
  60. * batch; if `batchSize` is 0, returns the number of consecutive ids minted so far.
  61. *
  62. * Requirements:
  63. *
  64. * - `batchSize` must not be greater than {_maxBatchSize}.
  65. * - The function is called in the constructor of the contract (directly or indirectly).
  66. *
  67. * CAUTION: Does not emit a `Transfer` event. This is ERC721 compliant as long as it is done outside of the
  68. * constructor, which is enforced by this function.
  69. *
  70. * CAUTION: Does not invoke `onERC721Received` on the receiver.
  71. *
  72. * Emits a {IERC2309-ConsecutiveTransfer} event.
  73. */
  74. function _mintConsecutive(address to, uint96 batchSize) internal virtual returns (uint96) {
  75. uint96 first = _totalConsecutiveSupply();
  76. // minting a batch of size 0 is a no-op
  77. if (batchSize > 0) {
  78. require(!Address.isContract(address(this)), "ERC721Consecutive: batch minting restricted to constructor");
  79. require(to != address(0), "ERC721Consecutive: mint to the zero address");
  80. require(batchSize <= _maxBatchSize(), "ERC721Consecutive: batch too large");
  81. // hook before
  82. _beforeConsecutiveTokenTransfer(address(0), to, first, batchSize);
  83. // push an ownership checkpoint & emit event
  84. uint96 last = first + batchSize - 1;
  85. _sequentialOwnership.push(last, uint160(to));
  86. emit ConsecutiveTransfer(first, last, address(0), to);
  87. // hook after
  88. _afterConsecutiveTokenTransfer(address(0), to, first, batchSize);
  89. }
  90. return first;
  91. }
  92. /**
  93. * @dev See {ERC721-_mint}. Override version that restricts normal minting to after construction.
  94. *
  95. * Warning: Using {ERC721Consecutive} prevents using {_mint} during construction in favor of {_mintConsecutive}.
  96. * After construction, {_mintConsecutive} is no longer available and {_mint} becomes available.
  97. */
  98. function _mint(address to, uint256 tokenId) internal virtual override {
  99. require(Address.isContract(address(this)), "ERC721Consecutive: can't mint during construction");
  100. super._mint(to, tokenId);
  101. }
  102. /**
  103. * @dev See {ERC721-_afterTokenTransfer}. Burning of tokens that have been sequentially minted must be explicit.
  104. */
  105. function _afterTokenTransfer(
  106. address from,
  107. address to,
  108. uint256 tokenId
  109. ) internal virtual override {
  110. if (
  111. to == address(0) && // if we burn
  112. tokenId < _totalConsecutiveSupply() && // and the tokenId was minted in a batch
  113. !_sequentialBurn.get(tokenId) // and the token was never marked as burnt
  114. ) {
  115. _sequentialBurn.set(tokenId);
  116. }
  117. super._afterTokenTransfer(from, to, tokenId);
  118. }
  119. function _totalConsecutiveSupply() private view returns (uint96) {
  120. (bool exists, uint96 latestId, ) = _sequentialOwnership.latestCheckpoint();
  121. return exists ? latestId + 1 : 0;
  122. }
  123. }