ERC721Consecutive.sol 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. // SPDX-License-Identifier: MIT
  2. // OpenZeppelin Contracts (last updated v4.8.0-rc.0) (token/ERC721/extensions/ERC721Consecutive.sol)
  3. pragma solidity ^0.8.0;
  4. import "../ERC721.sol";
  5. import "../../../interfaces/IERC2309.sol";
  6. import "../../../utils/Checkpoints.sol";
  7. import "../../../utils/math/SafeCast.sol";
  8. import "../../../utils/structs/BitMaps.sol";
  9. /**
  10. * @dev Implementation of the ERC2309 "Consecutive Transfer Extension" as defined in
  11. * https://eips.ethereum.org/EIPS/eip-2309[EIP-2309].
  12. *
  13. * This extension allows the minting of large batches of tokens during the contract construction. These batches are
  14. * limited to 5000 tokens at a time to accommodate off-chain indexers.
  15. *
  16. * Using this extension removes the ability to mint single tokens during the 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. When using this extension, you should consider the {_beforeConsecutiveTokenTransfer} and
  21. * {_afterConsecutiveTokenTransfer} hooks in addition to {_beforeTokenTransfer} and {_afterTokenTransfer}.
  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. * _Available since v4.8._
  28. */
  29. abstract contract ERC721Consecutive is IERC2309, ERC721 {
  30. using BitMaps for BitMaps.BitMap;
  31. using Checkpoints for Checkpoints.Trace160;
  32. Checkpoints.Trace160 private _sequentialOwnership;
  33. BitMaps.BitMap private _sequentialBurn;
  34. /**
  35. * @dev See {ERC721-_ownerOf}. Override that checks the sequential ownership structure for tokens that have
  36. * been minted as part of a batch, and not yet transferred.
  37. */
  38. function _ownerOf(uint256 tokenId) internal view virtual override returns (address) {
  39. address owner = super._ownerOf(tokenId);
  40. // If token is owned by the core, or beyond consecutive range, return base value
  41. if (owner != address(0) || tokenId > type(uint96).max) {
  42. return owner;
  43. }
  44. // Otherwise, check the token was not burned, and fetch ownership from the anchors
  45. // Note: no need for safe cast, we know that tokenId <= type(uint96).max
  46. return _sequentialBurn.get(tokenId) ? address(0) : address(_sequentialOwnership.lowerLookup(uint96(tokenId)));
  47. }
  48. /**
  49. * @dev Mint a batch of tokens of length `batchSize` for `to`.
  50. *
  51. * WARNING: Consecutive mint is only available during construction. ERC721 requires that any minting done after
  52. * construction emits a `Transfer` event, which is not the case of mints performed using this function.
  53. *
  54. * WARNING: Consecutive mint is limited to batches of 5000 tokens. Further minting is possible from a contract's
  55. * point of view but would cause indexing issues for off-chain services.
  56. *
  57. * Emits a {ConsecutiveTransfer} event.
  58. */
  59. function _mintConsecutive(address to, uint96 batchSize) internal virtual returns (uint96) {
  60. uint96 first = _totalConsecutiveSupply();
  61. // minting a batch of size 0 is a no-op
  62. if (batchSize > 0) {
  63. require(!Address.isContract(address(this)), "ERC721Consecutive: batch minting restricted to constructor");
  64. require(to != address(0), "ERC721Consecutive: mint to the zero address");
  65. require(batchSize <= 5000, "ERC721Consecutive: batch too large");
  66. // hook before
  67. _beforeConsecutiveTokenTransfer(address(0), to, first, batchSize);
  68. // push an ownership checkpoint & emit event
  69. uint96 last = first + batchSize - 1;
  70. _sequentialOwnership.push(last, uint160(to));
  71. emit ConsecutiveTransfer(first, last, address(0), to);
  72. // hook after
  73. _afterConsecutiveTokenTransfer(address(0), to, first, batchSize);
  74. }
  75. return first;
  76. }
  77. /**
  78. * @dev See {ERC721-_mint}. Override version that restricts normal minting to after construction.
  79. *
  80. * Warning: Using {ERC721Consecutive} prevents using {_mint} during construction in favor of {_mintConsecutive}.
  81. * After construction, {_mintConsecutive} is no longer available and {_mint} becomes available.
  82. */
  83. function _mint(address to, uint256 tokenId) internal virtual override {
  84. require(Address.isContract(address(this)), "ERC721Consecutive: can't mint during construction");
  85. super._mint(to, tokenId);
  86. }
  87. /**
  88. * @dev See {ERC721-_afterTokenTransfer}. Burning of token that have been sequentially minted must be explicit.
  89. */
  90. function _afterTokenTransfer(
  91. address from,
  92. address to,
  93. uint256 tokenId
  94. ) internal virtual override {
  95. if (
  96. to == address(0) && // if we burn
  97. tokenId <= _totalConsecutiveSupply() && // and the tokenId was minted is a batch
  98. !_sequentialBurn.get(tokenId) // and the token was never marked as burnt
  99. ) {
  100. _sequentialBurn.set(tokenId);
  101. }
  102. super._afterTokenTransfer(from, to, tokenId);
  103. }
  104. function _totalConsecutiveSupply() private view returns (uint96) {
  105. (bool exists, uint96 latestId, ) = _sequentialOwnership.latestCheckpoint();
  106. return exists ? latestId + 1 : 0;
  107. }
  108. }