diff --git a/contracts/mocks/ERC721ConsecutiveMock.sol b/contracts/mocks/ERC721ConsecutiveMock.sol index add5ab160d3..34fb5d037b4 100644 --- a/contracts/mocks/ERC721ConsecutiveMock.sol +++ b/contracts/mocks/ERC721ConsecutiveMock.sol @@ -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 @@ -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)) } } diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index 3e21bfd9e3a..1ab7b7f8c44 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -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: * @@ -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: * @@ -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 {} } diff --git a/contracts/token/ERC721/extensions/ERC721Consecutive.sol b/contracts/token/ERC721/extensions/ERC721Consecutive.sol index e8e97aab657..e016afd9bfa 100644 --- a/contracts/token/ERC721/extensions/ERC721Consecutive.sol +++ b/contracts/token/ERC721/extensions/ERC721Consecutive.sol @@ -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 @@ -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. @@ -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. */ @@ -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. * @@ -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(); @@ -87,10 +113,17 @@ 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; @@ -98,7 +131,11 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 { 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; @@ -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 { @@ -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) { diff --git a/contracts/token/ERC721/extensions/ERC721Enumerable.sol b/contracts/token/ERC721/extensions/ERC721Enumerable.sol index 6f8573cba03..17de291b28a 100644 --- a/contracts/token/ERC721/extensions/ERC721Enumerable.sol +++ b/contracts/token/ERC721/extensions/ERC721Enumerable.sol @@ -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 diff --git a/contracts/token/ERC721/extensions/ERC721Pausable.sol b/contracts/token/ERC721/extensions/ERC721Pausable.sol index 28972b63a31..a77aaef1fa1 100644 --- a/contracts/token/ERC721/extensions/ERC721Pausable.sol +++ b/contracts/token/ERC721/extensions/ERC721Pausable.sol @@ -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"); + } } diff --git a/contracts/token/ERC721/extensions/draft-ERC721Votes.sol b/contracts/token/ERC721/extensions/draft-ERC721Votes.sol index ea9227ab015..b49110d02b6 100644 --- a/contracts/token/ERC721/extensions/draft-ERC721Votes.sol +++ b/contracts/token/ERC721/extensions/draft-ERC721Votes.sol @@ -5,4 +5,54 @@ pragma solidity ^0.8.0; // ERC721Votes was marked as draft due to the EIP-712 dependency. // EIP-712 is Final as of 2022-08-11. This file is deprecated. +<<<<<<< HEAD import "./ERC721Votes.sol"; +======= +/** + * @dev Extension of ERC721 to support voting and delegation as implemented by {Votes}, where each individual NFT counts + * as 1 vote unit. + * + * Tokens do not count as votes until they are delegated, because votes must be tracked which incurs an additional cost + * on every transfer. Token holders can either delegate to a trusted representative who will decide how to make use of + * the votes in governance decisions, or they can delegate to themselves to be their own representative. + * + * _Available since v4.5._ + */ +abstract contract ERC721Votes is ERC721, Votes { + /** + * @dev Adjusts votes when tokens are transferred. + * + * Emits a {Votes-DelegateVotesChanged} event. + */ + function _afterTokenTransfer( + address from, + address to, + uint256 tokenId + ) internal virtual override { + _transferVotingUnits(from, to, 1); + super._afterTokenTransfer(from, to, tokenId); + } + + /** + * @dev Adjusts votes when a batch of tokens is transferred. + * + * Emits a {Votes-DelegateVotesChanged} event. + */ + function _afterConsecutiveTokenTransfer( + address from, + address to, + uint256 first, + uint96 size + ) internal virtual override { + _transferVotingUnits(from, to, size); + super._afterConsecutiveTokenTransfer(from, to, first, size); + } + + /** + * @dev Returns the balance of `account`. + */ + function _getVotingUnits(address account) internal view virtual override returns (uint256) { + return balanceOf(account); + } +} +>>>>>>> a69e9666 (ERC721 extension for efficient batch minting (#3311)) diff --git a/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol b/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol index c2ff99d7ec4..538933c91cc 100644 --- a/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol +++ b/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol @@ -125,6 +125,15 @@ contract ERC721PresetMinterPauserAutoId is super._beforeTokenTransfer(from, to, firstTokenId, batchSize); } + function _beforeConsecutiveTokenTransfer( + address from, + address to, + uint256 first, + uint96 size + ) internal virtual override(ERC721, ERC721Enumerable, ERC721Pausable) { + super._beforeConsecutiveTokenTransfer(from, to, first, size); + } + /** * @dev See {IERC165-supportsInterface}. */ diff --git a/contracts/utils/Checkpoints.sol b/contracts/utils/Checkpoints.sol index 81a8b5d5a17..bfd7bac46d1 100644 --- a/contracts/utils/Checkpoints.sol +++ b/contracts/utils/Checkpoints.sol @@ -261,6 +261,17 @@ library Checkpoints { } /** +<<<<<<< HEAD +======= + * @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + */ + function latest(Trace224 storage self) internal view returns (uint224) { + uint256 pos = self._checkpoints.length; + return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; + } + + /** +>>>>>>> a69e9666 (ERC721 extension for efficient batch minting (#3311)) * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value * in the most recent checkpoint. */ diff --git a/test/token/ERC721/extensions/ERC721Consecutive.test.js b/test/token/ERC721/extensions/ERC721Consecutive.test.js index 5e480abb855..05ace93e012 100644 --- a/test/token/ERC721/extensions/ERC721Consecutive.test.js +++ b/test/token/ERC721/extensions/ERC721Consecutive.test.js @@ -122,7 +122,11 @@ contract('ERC721Consecutive', function (accounts) { expect(await this.token.ownerOf(1)).to.be.equal(receiver); }); +<<<<<<< HEAD it('tokens can be burned and re-minted #1', async function () { +======= + it('tokens can be burned and re-minted', async function () { +>>>>>>> a69e9666 (ERC721 extension for efficient batch minting (#3311)) expectEvent( await this.token.burn(1, { from: user1 }), 'Transfer', @@ -139,6 +143,7 @@ contract('ERC721Consecutive', function (accounts) { expect(await this.token.ownerOf(1)).to.be.equal(user2); }); +<<<<<<< HEAD it('tokens can be burned and re-minted #2', async function () { const tokenId = batches.reduce((acc, { amount }) => acc.addn(amount), web3.utils.toBN(0)); @@ -172,6 +177,8 @@ contract('ERC721Consecutive', function (accounts) { expect(await this.token.exists(tokenId)).to.be.equal(true); expect(await this.token.ownerOf(tokenId), user2); }); +======= +>>>>>>> a69e9666 (ERC721 extension for efficient batch minting (#3311)) }); });