Skip to content

Commit

Permalink
Add ERC2309 mint function and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Vectorized committed Jun 7, 2022
1 parent 5388170 commit 6aa02db
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 0 deletions.
53 changes: 53 additions & 0 deletions contracts/ERC721A.sol
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,59 @@ contract ERC721A is IERC721A {
_afterTokenTransfers(address(0), to, startTokenId, quantity);
}

/**
* @dev Mints `quantity` tokens and transfers them to `to`.
*
* This function is intended for efficient minting during contract creation.
*
* It emits only one {ConsecutiveTransfer} as defined in
* [ERC2309](https://eips.ethereum.org/EIPS/eip-2309),
* instead of one or more {Transfer} events.
*
* Calling this function outside of contract creation WILL break the ERC721 standard.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `quantity` must be greater than 0.
*
* Emits a {ConsecutiveTransfer} event.
*/
function _mintERC2309(address to, uint256 quantity) internal {
uint256 startTokenId = _currentIndex;
if (_addressToUint256(to) == 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 {
// Updates:
// - `balance += quantity`.
// - `numberMinted += quantity`.
//
// We can directly add to the balance and number minted.
_packedAddressData[to] += quantity * ((1 << BITPOS_NUMBER_MINTED) | 1);

// Updates:
// - `address` to the owner.
// - `startTimestamp` to the timestamp of minting.
// - `burned` to `false`.
// - `nextInitialized` to `quantity == 1`.
_packedOwnerships[startTokenId] =
_addressToUint256(to) |
(block.timestamp << BITPOS_START_TIMESTAMP) |
(_boolToUint256(quantity == 1) << BITPOS_NEXT_INITIALIZED);

emit ConsecutiveTransfer(startTokenId, startTokenId + quantity - 1, address(0), to);

_currentIndex = startTokenId + quantity;
}
_afterTokenTransfers(address(0), to, startTokenId, quantity);
}

/**
* @dev Transfers `tokenId` from `from` to `to`.
*
Expand Down
9 changes: 9 additions & 0 deletions contracts/IERC721A.sol
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,13 @@ interface IERC721A {
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);

// ==============================
// IERC2309
// ==============================

/**
* @dev Emitted when tokens in `fromTokenId` to `toTokenId` (inclusive) is transferred from `from` to `to.
*/
event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed from, address indexed to);
}
23 changes: 23 additions & 0 deletions contracts/mocks/ERC721AWithERC2309Mock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.0.0
// Creators: Chiru Labs

pragma solidity ^0.8.4;

import '../ERC721A.sol';

/**
* @dev Mock for testing and benchmarking purposes.
* Calling `_mintERC2309` outside of contract creation breaks the ERC721 standard.
*/
contract ERC721AWithERC2309Mock is ERC721A {
constructor(string memory name_, string memory symbol_) ERC721A(name_, symbol_) {}

function mintOneERC2309(address to) public {
_mintERC2309(to, 1);
}

function mintTenERC2309(address to) public {
_mintERC2309(to, 10);
}
}
24 changes: 24 additions & 0 deletions test/ERC721A.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -680,3 +680,27 @@ describe(
'ERC721A override _startTokenId()',
createTestSuite({ contract: 'ERC721AStartTokenIdMock', constructorArgs: ['Azuki', 'AZUKI', 1] })
);

describe('ERC721A with ERC2309', async function () {
beforeEach(async function () {
this.erc721a = await deployContract('ERC721AWithERC2309Mock', ['Azuki', 'AZUKI']);
const [owner, addr1] = await ethers.getSigners();
this.owner = owner;
this.addr1 = addr1;
this.testEmit = async (methodName, fromTokenId, toTokenId) => {
await expect(await this.erc721a[methodName](this.addr1.address))
.to.emit(this.erc721a, 'ConsecutiveTransfer')
.withArgs(fromTokenId, toTokenId, ZERO_ADDRESS, this.addr1.address);
};
});

it('emits a ConsecutiveTransfer event for single mint', async function () {
await this.testEmit('mintOneERC2309', 0, 0);
await this.testEmit('mintOneERC2309', 1, 1);
});

it('emits a ConsecutiveTransfer event for batch mint', async function () {
await this.testEmit('mintTenERC2309', 0, 9);
await this.testEmit('mintTenERC2309', 10, 19);
});
});
14 changes: 14 additions & 0 deletions test/GasUsage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ describe('ERC721A Gas Usage', function () {
});
});

it('mintOneERC2309', async function () {
let contract = await deployContract('ERC721AWithERC2309Mock', ['Azuki', 'AZUKI']);
await contract.mintOneERC2309(this.owner.address);
await contract.mintOneERC2309(this.owner.address);
await contract.mintOneERC2309(this.addr1.address);
});

it('mintTenERC2309', async function () {
let contract = await deployContract('ERC721AWithERC2309Mock', ['Azuki', 'AZUKI']);
await contract.mintTenERC2309(this.owner.address);
await contract.mintTenERC2309(this.owner.address);
await contract.mintTenERC2309(this.addr1.address);
});

context('transferFrom', function () {
beforeEach(async function () {
await this.erc721a.mintTen(this.owner.address);
Expand Down

0 comments on commit 6aa02db

Please sign in to comment.