diff --git a/contracts/ERC721A.sol b/contracts/ERC721A.sol index cf05fcf7b..43bbf1808 100644 --- a/contracts/ERC721A.sol +++ b/contracts/ERC721A.sol @@ -329,6 +329,9 @@ contract ERC721A is Context, ERC165, IERC721, IERC721Metadata { return _startTokenId() <= tokenId && tokenId < _currentIndex && !_ownerships[tokenId].burned; } + /** + * @dev Equivalent to `_safeMint(to, quantity, '')`. + */ function _safeMint(address to, uint256 quantity) internal { _safeMint(to, quantity, ''); } @@ -338,7 +341,8 @@ contract ERC721A is Context, ERC165, IERC721, IERC721Metadata { * * Requirements: * - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called for each safe transfer. + * - If `to` refers to a smart contract, it must implement + * {IERC721Receiver-onERC721Received}, which is called for each safe transfer. * - `quantity` must be greater than 0. * * Emits a {Transfer} event. @@ -347,25 +351,6 @@ contract ERC721A is Context, ERC165, IERC721, IERC721Metadata { address to, uint256 quantity, bytes memory _data - ) internal { - _mint(to, quantity, _data, true); - } - - /** - * @dev Mints `quantity` tokens and transfers them to `to`. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - `quantity` must be greater than 0. - * - * Emits a {Transfer} event. - */ - function _mint( - address to, - uint256 quantity, - bytes memory _data, - bool safe ) internal { uint256 startTokenId = _currentIndex; if (to == address(0)) revert MintToZeroAddress(); @@ -386,7 +371,7 @@ contract ERC721A is Context, ERC165, IERC721, IERC721Metadata { uint256 updatedIndex = startTokenId; uint256 end = updatedIndex + quantity; - if (safe && to.isContract()) { + if (to.isContract()) { do { emit Transfer(address(0), to, updatedIndex); if (!_checkContractOnERC721Received(address(0), to, updatedIndex++, _data)) { @@ -405,6 +390,45 @@ contract ERC721A is Context, ERC165, IERC721, IERC721Metadata { _afterTokenTransfers(address(0), to, startTokenId, quantity); } + /** + * @dev Mints `quantity` tokens and transfers them to `to`. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `quantity` must be greater than 0. + * + * Emits a {Transfer} event. + */ + function _mint(address to, uint256 quantity) internal { + uint256 startTokenId = _currentIndex; + if (to == address(0)) revert MintToZeroAddress(); + if (quantity == 0) revert MintZeroQuantity(); + + _beforeTokenTransfers(address(0), to, startTokenId, quantity); + + // Overflows are incredibly unrealistic. + // balance or numberMinted overflow if current value of either + quantity > 1.8e19 (2**64) - 1 + // updatedIndex overflows if _currentIndex + quantity > 1.2e77 (2**256) - 1 + unchecked { + _addressData[to].balance += uint64(quantity); + _addressData[to].numberMinted += uint64(quantity); + + _ownerships[startTokenId].addr = to; + _ownerships[startTokenId].startTimestamp = uint64(block.timestamp); + + uint256 updatedIndex = startTokenId; + uint256 end = updatedIndex + quantity; + + do { + emit Transfer(address(0), to, updatedIndex++); + } while (updatedIndex != end); + + _currentIndex = updatedIndex; + } + _afterTokenTransfers(address(0), to, startTokenId, quantity); + } + /** * @dev Transfers `tokenId` from `from` to `to`. * @@ -466,7 +490,7 @@ contract ERC721A is Context, ERC165, IERC721, IERC721Metadata { } /** - * @dev This is equivalent to _burn(tokenId, false) + * @dev Equivalent to `_burn(tokenId, false)`. */ function _burn(uint256 tokenId) internal virtual { _burn(tokenId, false); diff --git a/contracts/mocks/ERC721AGasReporterMock.sol b/contracts/mocks/ERC721AGasReporterMock.sol index 7b6d8085d..8531750fd 100644 --- a/contracts/mocks/ERC721AGasReporterMock.sol +++ b/contracts/mocks/ERC721AGasReporterMock.sol @@ -13,7 +13,7 @@ contract ERC721AGasReporterMock is ERC721A { } function mintOne(address to) public { - _mint(to, 1, '', false); + _mint(to, 1); } function safeMintTen(address to) public { @@ -21,6 +21,6 @@ contract ERC721AGasReporterMock is ERC721A { } function mintTen(address to) public { - _mint(to, 10, '', false); + _mint(to, 10); } } diff --git a/contracts/mocks/ERC721AMock.sol b/contracts/mocks/ERC721AMock.sol index 6c2685d75..8395cb0cf 100644 --- a/contracts/mocks/ERC721AMock.sol +++ b/contracts/mocks/ERC721AMock.sol @@ -36,21 +36,12 @@ contract ERC721AMock is ERC721A { _safeMint(to, quantity); } - function safeMint( - address to, - uint256 quantity, - bytes memory _data - ) public { + function safeMint(address to, uint256 quantity, bytes memory _data) public { _safeMint(to, quantity, _data); } - function mint( - address to, - uint256 quantity, - bytes memory _data, - bool safe - ) public { - _mint(to, quantity, _data, safe); + function mint(address to, uint256 quantity) public { + _mint(to, quantity); } function burn(uint256 tokenId, bool approvalCheck) public { diff --git a/test/ERC721A.test.js b/test/ERC721A.test.js index 613cf0694..765452fc3 100644 --- a/test/ERC721A.test.js +++ b/test/ERC721A.test.js @@ -318,10 +318,8 @@ const createTestSuite = ({ contract, constructorArgs }) => }); describe('mint', function () { - const data = '0x42'; - it('successfully mints a single token', async function () { - const mintTx = await this.erc721a.mint(this.receiver.address, 1, data, false); + const mintTx = await this.erc721a.mint(this.receiver.address, 1); await expect(mintTx) .to.emit(this.erc721a, 'Transfer') .withArgs(ZERO_ADDRESS, this.receiver.address, this.startTokenId); @@ -330,7 +328,7 @@ const createTestSuite = ({ contract, constructorArgs }) => }); it('successfully mints multiple tokens', async function () { - const mintTx = await this.erc721a.mint(this.receiver.address, 5, data, false); + const mintTx = await this.erc721a.mint(this.receiver.address, 5); for (let tokenId = this.startTokenId; tokenId < 5 + this.startTokenId; tokenId++) { await expect(mintTx) .to.emit(this.erc721a, 'Transfer') @@ -342,16 +340,16 @@ const createTestSuite = ({ contract, constructorArgs }) => it('does not revert for non-receivers', async function () { const nonReceiver = this.erc721a; - await this.erc721a.mint(nonReceiver.address, 1, data, false); + await this.erc721a.mint(nonReceiver.address, 1); expect(await this.erc721a.ownerOf(this.startTokenId)).to.equal(nonReceiver.address); }); it('rejects mints to the zero address', async function () { - await expect(this.erc721a.mint(ZERO_ADDRESS, 1, data, false)).to.be.revertedWith('MintToZeroAddress'); + await expect(this.erc721a.mint(ZERO_ADDRESS, 1)).to.be.revertedWith('MintToZeroAddress'); }); it('requires quantity to be greater than 0', async function () { - await expect(this.erc721a.mint(this.owner.address, 0, data, false)).to.be.revertedWith('MintZeroQuantity'); + await expect(this.erc721a.mint(this.owner.address, 0)).to.be.revertedWith('MintZeroQuantity'); }); }); });