Skip to content

Commit

Permalink
ERC721 extension for efficient batch minting (OpenZeppelin#3311)
Browse files Browse the repository at this point in the history
Co-authored-by: Francisco <frangio.1@gmail.com>
  • Loading branch information
2 people authored and JulissaDantes committed Nov 4, 2022
1 parent 54455d5 commit b57beee
Show file tree
Hide file tree
Showing 9 changed files with 298 additions and 0 deletions.
90 changes: 90 additions & 0 deletions contracts/mocks/ERC721ConsecutiveMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import "../token/ERC721/extensions/ERC721Burnable.sol";
import "../token/ERC721/extensions/ERC721Consecutive.sol";
import "../token/ERC721/extensions/ERC721Enumerable.sol";
import "../token/ERC721/extensions/ERC721Pausable.sol";
<<<<<<< HEAD
import "../token/ERC721/extensions/ERC721Votes.sol";
=======
import "../token/ERC721/extensions/draft-ERC721Votes.sol";
>>>>>>> a69e9666 (ERC721 extension for efficient batch minting (#3311))

/**
* @title ERC721ConsecutiveMock
Expand Down Expand Up @@ -63,19 +67,105 @@ contract ERC721ConsecutiveMock is ERC721Burnable, ERC721Consecutive, ERC721Pausa
function _beforeTokenTransfer(
address from,
address to,
<<<<<<< HEAD
uint256 firstTokenId,
uint256 batchSize
) internal virtual override(ERC721, ERC721Pausable) {
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
=======
uint256 tokenId
) internal virtual override(ERC721, ERC721Pausable) {
super._beforeTokenTransfer(from, to, tokenId);
>>>>>>> a69e9666 (ERC721 extension for efficient batch minting (#3311))
}

function _afterTokenTransfer(
address from,
address to,
<<<<<<< HEAD
uint256 firstTokenId,
uint256 batchSize
) internal virtual override(ERC721, ERC721Votes, ERC721Consecutive) {
super._afterTokenTransfer(from, to, firstTokenId, batchSize);
=======
uint256 tokenId
) internal virtual override(ERC721, ERC721Votes, ERC721Consecutive) {
super._afterTokenTransfer(from, to, tokenId);
}

function _beforeConsecutiveTokenTransfer(
address from,
address to,
uint256 first,
uint96 size
) internal virtual override(ERC721, ERC721Pausable) {
super._beforeConsecutiveTokenTransfer(from, to, first, size);
}

function _afterConsecutiveTokenTransfer(
address from,
address to,
uint256 first,
uint96 size
) internal virtual override(ERC721, ERC721Votes) {
super._afterConsecutiveTokenTransfer(from, to, first, size);
}
}

contract ERC721ConsecutiveEnumerableMock is ERC721Consecutive, ERC721Enumerable {
constructor(
string memory name,
string memory symbol,
address[] memory receivers,
uint96[] memory amounts
) ERC721(name, symbol) {
for (uint256 i = 0; i < receivers.length; ++i) {
_mintConsecutive(receivers[i], amounts[i]);
}
}

function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC721, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}

function _ownerOf(uint256 tokenId) internal view virtual override(ERC721, ERC721Consecutive) returns (address) {
return super._ownerOf(tokenId);
}

function _mint(address to, uint256 tokenId) internal virtual override(ERC721, ERC721Consecutive) {
super._mint(to, tokenId);
}

function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual override(ERC721, ERC721Enumerable) {
super._beforeTokenTransfer(from, to, tokenId);
}

function _afterTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual override(ERC721, ERC721Consecutive) {
super._afterTokenTransfer(from, to, tokenId);
}

function _beforeConsecutiveTokenTransfer(
address from,
address to,
uint256 first,
uint96 size
) internal virtual override(ERC721, ERC721Enumerable) {
super._beforeConsecutiveTokenTransfer(from, to, first, size);
>>>>>>> a69e9666 (ERC721 extension for efficient batch minting (#3311))
}
}

Expand Down
42 changes: 42 additions & 0 deletions contracts/token/ERC721/ERC721.sol
Original file line number Diff line number Diff line change
Expand Up @@ -451,8 +451,13 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
}

/**
<<<<<<< HEAD
* @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is
* used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
=======
* @dev Hook that is called before any (single) token transfer. This includes minting and burning.
* See {_beforeConsecutiveTokenTransfer}.
>>>>>>> a69e9666 (ERC721 extension for efficient batch minting (#3311))
*
* Calling conditions:
*
Expand Down Expand Up @@ -481,8 +486,13 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
}

/**
<<<<<<< HEAD
* @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is
* used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
=======
* @dev Hook that is called after any (single) transfer of tokens. This includes minting and burning.
* See {_afterConsecutiveTokenTransfer}.
>>>>>>> a69e9666 (ERC721 extension for efficient batch minting (#3311))
*
* Calling conditions:
*
Expand All @@ -500,4 +510,36 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
uint256 firstTokenId,
uint256 batchSize
) internal virtual {}

/**
* @dev Hook that is called before consecutive token transfers.
* Calling conditions are similar to {_beforeTokenTransfer}.
*
* The default implementation include balances updates that extensions such as {ERC721Consecutive} cannot perform
* directly.
*/
function _beforeConsecutiveTokenTransfer(
address from,
address to,
uint256, /*first*/
uint96 size
) internal virtual {
if (from != address(0)) {
_balances[from] -= size;
}
if (to != address(0)) {
_balances[to] += size;
}
}

/**
* @dev Hook that is called after consecutive token transfers.
* Calling conditions are similar to {_afterTokenTransfer}.
*/
function _afterConsecutiveTokenTransfer(
address, /*from*/
address, /*to*/
uint256, /*first*/
uint96 /*size*/
) internal virtual {}
}
54 changes: 54 additions & 0 deletions contracts/token/ERC721/extensions/ERC721Consecutive.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,28 @@ pragma solidity ^0.8.0;
import "../ERC721.sol";
import "../../../interfaces/IERC2309.sol";
import "../../../utils/Checkpoints.sol";
<<<<<<< HEAD
=======
import "../../../utils/math/SafeCast.sol";
>>>>>>> a69e9666 (ERC721 extension for efficient batch minting (#3311))
import "../../../utils/structs/BitMaps.sol";

/**
* @dev Implementation of the ERC2309 "Consecutive Transfer Extension" as defined in
* https://eips.ethereum.org/EIPS/eip-2309[EIP-2309].
*
<<<<<<< HEAD
* This extension allows the minting of large batches of tokens, during contract construction only. For upgradeable
* contracts this implies that batch minting is only available during proxy deployment, and not in subsequent upgrades.
* These batches are limited to 5000 tokens at a time by default to accommodate off-chain indexers.
*
* Using this extension removes the ability to mint single tokens during contract construction. This ability is
=======
* This extension allows the minting of large batches of tokens during the contract construction. These batches are
* limited to 5000 tokens at a time to accommodate off-chain indexers.
*
* Using this extension removes the ability to mint single tokens during the contract construction. This ability is
>>>>>>> a69e9666 (ERC721 extension for efficient batch minting (#3311))
* regained after construction. During construction, only batch minting is allowed.
*
* IMPORTANT: This extension bypasses the hooks {_beforeTokenTransfer} and {_afterTokenTransfer} for tokens minted in
Expand All @@ -36,6 +47,7 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 {
BitMaps.BitMap private _sequentialBurn;

/**
<<<<<<< HEAD
* @dev Maximum size of a batch of consecutive tokens. This is designed to limit stress on off-chain indexing
* services that have to record one entry per token, and have protections against "unreasonably large" batches of
* tokens.
Expand All @@ -48,6 +60,8 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 {
}

/**
=======
>>>>>>> a69e9666 (ERC721 extension for efficient batch minting (#3311))
* @dev See {ERC721-_ownerOf}. Override that checks the sequential ownership structure for tokens that have
* been minted as part of a batch, and not yet transferred.
*/
Expand All @@ -65,6 +79,7 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 {
}

/**
<<<<<<< HEAD
* @dev Mint a batch of tokens of length `batchSize` for `to`. Returns the token id of the first token minted in the
* batch; if `batchSize` is 0, returns the number of consecutive ids minted so far.
*
Expand All @@ -79,6 +94,17 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 {
* CAUTION: Does not invoke `onERC721Received` on the receiver.
*
* Emits a {IERC2309-ConsecutiveTransfer} event.
=======
* @dev Mint a batch of tokens of length `batchSize` for `to`.
*
* WARNING: Consecutive mint is only available during construction. ERC721 requires that any minting done after
* construction emits a `Transfer` event, which is not the case of mints performed using this function.
*
* WARNING: Consecutive mint is limited to batches of 5000 tokens. Further minting is possible from a contract's
* point of view but would cause indexing issues for off-chain services.
*
* Emits a {ConsecutiveTransfer} event.
>>>>>>> a69e9666 (ERC721 extension for efficient batch minting (#3311))
*/
function _mintConsecutive(address to, uint96 batchSize) internal virtual returns (uint96) {
uint96 first = _totalConsecutiveSupply();
Expand All @@ -87,18 +113,29 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 {
if (batchSize > 0) {
require(!Address.isContract(address(this)), "ERC721Consecutive: batch minting restricted to constructor");
require(to != address(0), "ERC721Consecutive: mint to the zero address");
<<<<<<< HEAD
require(batchSize <= _maxBatchSize(), "ERC721Consecutive: batch too large");

// hook before
_beforeTokenTransfer(address(0), to, first, batchSize);
=======
require(batchSize <= 5000, "ERC721Consecutive: batch too large");

// hook before
_beforeConsecutiveTokenTransfer(address(0), to, first, batchSize);
>>>>>>> a69e9666 (ERC721 extension for efficient batch minting (#3311))

// push an ownership checkpoint & emit event
uint96 last = first + batchSize - 1;
_sequentialOwnership.push(last, uint160(to));
emit ConsecutiveTransfer(first, last, address(0), to);

// hook after
<<<<<<< HEAD
_afterTokenTransfer(address(0), to, first, batchSize);
=======
_afterConsecutiveTokenTransfer(address(0), to, first, batchSize);
>>>>>>> a69e9666 (ERC721 extension for efficient batch minting (#3311))
}

return first;
Expand All @@ -116,11 +153,16 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 {
}
/**
<<<<<<< HEAD
* @dev See {ERC721-_afterTokenTransfer}. Burning of tokens that have been sequentially minted must be explicit.
=======
* @dev See {ERC721-_afterTokenTransfer}. Burning of token that have been sequentially minted must be explicit.
>>>>>>> a69e9666 (ERC721 extension for efficient batch minting (#3311))
*/
function _afterTokenTransfer(
address from,
address to,
<<<<<<< HEAD
uint256 firstTokenId,
uint256 batchSize
) internal virtual override {
Expand All @@ -133,6 +175,18 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 {
_sequentialBurn.set(firstTokenId);
}
super._afterTokenTransfer(from, to, firstTokenId, batchSize);
=======
uint256 tokenId
) internal virtual override {
if (
to == address(0) && // if we burn
tokenId <= _totalConsecutiveSupply() && // and the tokenId was minted is a batch
!_sequentialBurn.get(tokenId) // and the token was never marked as burnt
) {
_sequentialBurn.set(tokenId);
}
super._afterTokenTransfer(from, to, tokenId);
>>>>>>> a69e9666 (ERC721 extension for efficient batch minting (#3311))
}
function _totalConsecutiveSupply() private view returns (uint96) {
Expand Down
24 changes: 24 additions & 0 deletions contracts/token/ERC721/extensions/ERC721Enumerable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,30 @@ abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {
}
}

/**
* @dev Hook that is called before any batch token transfer. For now this is limited
* to batch minting by the {ERC721Consecutive} extension.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
* transferred to `to`.
* - When `from` is zero, `tokenId` will be minted for `to`.
* - When `to` is zero, ``from``'s `tokenId` will be burned.
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeConsecutiveTokenTransfer(
address,
address,
uint256,
uint96
) internal virtual override {
revert("ERC721Enumerable: consecutive transfers not supported");
}

/**
* @dev Private function to add a token to this extension's ownership-tracking data structures.
* @param to address representing the new owner of the given token ID
Expand Down
11 changes: 11 additions & 0 deletions contracts/token/ERC721/extensions/ERC721Pausable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,15 @@ abstract contract ERC721Pausable is ERC721, Pausable {

require(!paused(), "ERC721Pausable: token transfer while paused");
}

function _beforeConsecutiveTokenTransfer(
address from,
address to,
uint256 first,
uint96 size
) internal virtual override {
super._beforeConsecutiveTokenTransfer(from, to, first, size);

require(!paused(), "ERC721Pausable: token transfer while paused");
}
}

0 comments on commit b57beee

Please sign in to comment.