Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ERC721ABatchBurnable extension #444

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
6e4425c
added comments on transfer hooks
jjranalli Jan 17, 2023
06f89f3
added sort
jjranalli Jan 17, 2023
6d7c64f
added clearApprovalsAndEmitTransferEvent
jjranalli Jan 17, 2023
7a5a17e
added tokenBatchTransfer hooks
jjranalli Jan 17, 2023
b85b741
added _batchTransferFrom and safe variants
jjranalli Jan 17, 2023
c6da1dc
added ERC721ABatchTransferable extension and interface
jjranalli Jan 17, 2023
9115cfe
formatting
jjranalli Jan 17, 2023
571f7e1
added interface and ERC721ABatchTransferableMock
jjranalli Jan 17, 2023
c26ce33
added ERC721ABatchTransferable tests (wip)
jjranalli Jan 17, 2023
05c1b6f
added approvalCheck
jjranalli Jan 18, 2023
f52a3b8
fixed duplicate call
jjranalli Jan 20, 2023
7a135c4
comment
jjranalli Jan 20, 2023
d454e3a
fixed next initialized
jjranalli Jan 20, 2023
664882d
refactored lastInitPackedOwnership to use prevPackedOwnership
jjranalli Jan 20, 2023
64a88c4
comments
jjranalli Jan 20, 2023
96d7fb3
ensured correctness of nextInitialized in slots of transferred token Ids
jjranalli Jan 21, 2023
6a97e3b
renamed variables
jjranalli Jan 21, 2023
3aa66fd
reverted to leave nextInitialized unchanged
jjranalli Jan 25, 2023
a2de892
comment
jjranalli Jan 25, 2023
96bb77f
replace sort -> insertion sort
jjranalli Jan 25, 2023
1243741
bump: prettier-plugin-solidity
jjranalli Jan 25, 2023
67b710b
prettier
jjranalli Jan 25, 2023
5987697
added prettier-ignore
jjranalli Jan 25, 2023
798a7fc
Merge remote-tracking branch 'chiru-labs/main' into main
jjranalli Feb 17, 2023
52a2059
Merge branch 'main' into ERC721ABatchTransferable
jjranalli Feb 17, 2023
7643010
fixed nextTokenId in last array element
jjranalli Feb 17, 2023
c8ecb6a
tests wip
jjranalli Feb 17, 2023
8e8987b
refactor
jjranalli Feb 17, 2023
b707c92
updated BatchTransferable mock and extension
jjranalli Feb 17, 2023
e71a60c
updated tests
jjranalli Feb 17, 2023
ae13aac
add approval tests
jjranalli Feb 17, 2023
62d0af4
lint
jjranalli Feb 17, 2023
ce6bed4
lint fix
jjranalli Feb 18, 2023
951afa3
restore original .prettierrc
jjranalli Feb 18, 2023
0b9ce74
fix
jjranalli Feb 18, 2023
76c5bf6
comments and refactor
jjranalli Feb 20, 2023
9e91b64
added _batchBurn
jjranalli Feb 20, 2023
ca0fa7c
added ERC721ABatchBurnable extension, interfaces and mock
jjranalli Feb 20, 2023
45d86b4
fixed _batchBurn
jjranalli Feb 20, 2023
8d0d63e
fixed update of last tokenId + 1
jjranalli Feb 20, 2023
4aab6e5
batchBurnable tests wip
jjranalli Feb 20, 2023
6c5c730
refactor
jjranalli Feb 20, 2023
7357f34
fix
jjranalli Feb 20, 2023
35813a7
add auto-clearing of consecutive ids and set `nextInitialized` to false
jjranalli Feb 20, 2023
ddc6bd4
batchTransfer tests refactor
jjranalli Feb 20, 2023
091870d
tests wip
jjranalli Feb 20, 2023
5fea25d
tests wip
jjranalli Feb 20, 2023
ea7a3c2
comments
jjranalli Feb 20, 2023
0246402
added extraData logic to batch mocks
jjranalli Feb 20, 2023
3949159
updated batch tests
jjranalli Feb 20, 2023
8c3f497
refactored ERC721A to use _updateTokenId
jjranalli Feb 20, 2023
735c366
wip
jjranalli Feb 20, 2023
a22c2f5
comment
jjranalli Feb 21, 2023
4c11d74
Add ERC721ABatchBurnableMock (#450)
Vectorized Feb 22, 2023
411cf39
change tokenIds in ascending order in test
jjranalli Mar 25, 2023
38ad644
Merge remote-tracking branch 'chiru-labs/batchBurnTest' into ERC721AB…
jjranalli Mar 25, 2023
7a28bf0
removal of unneeded internal functions
jjranalli Mar 25, 2023
8e2fc6a
prettier
jjranalli Mar 25, 2023
9dac917
removed batch transfer logic
jjranalli Mar 25, 2023
43dc1f2
changed _updateTokenId
jjranalli Mar 25, 2023
6e04015
fixed mock
jjranalli Mar 25, 2023
172387b
fixed extension and mock
jjranalli Mar 25, 2023
a6c2823
fixed tests and cleaned unused functions in mock
jjranalli Mar 25, 2023
4d1e7dd
removed _updateTokenId
jjranalli Mar 25, 2023
c3438c0
minor gas optimizations
jjranalli Mar 25, 2023
424c82e
comment
jjranalli Mar 25, 2023
d4a2442
optimize: avoid potential double read from storage
jjranalli Mar 25, 2023
6c51c77
removed bulkBurn from mock
jjranalli Mar 25, 2023
e28b998
optimization: reset _packedOwnerships for initialized sequential IDs
jjranalli Mar 26, 2023
7e36bbb
added tests for sequential ID clearing
jjranalli Mar 26, 2023
4750482
added test for tokenIds in strictly ascending order
jjranalli Mar 26, 2023
2b2aafc
comment
jjranalli Mar 26, 2023
229bea6
optimize: keep track of prevTokenOwner to bypass duplicated logic
jjranalli Mar 26, 2023
c726988
revert: resetting _packedOwnerships in initialized sequential IDs
jjranalli Mar 26, 2023
90dcfe3
cleanup
jjranalli Mar 26, 2023
6f07d87
optimize: avoid potential double read from storage
jjranalli Mar 26, 2023
2cdff65
optimize: removed unneeded exists() via getApproved
jjranalli Mar 26, 2023
077dbf3
Merge branch 'batchBurnable' into ERC721ABatchTransferable
Vectorized Aug 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
}
}
]
}
}
133 changes: 133 additions & 0 deletions contracts/ERC721A.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,130 @@ contract ERC721A is IERC721A {
}
}

/**
* @dev Equivalent to `_batchBurn(msg.sender, tokenIds, false)`.
*/
function _batchBurn(uint256[] memory tokenIds) internal virtual {
_batchBurn(_msgSenderERC721A(), tokenIds, false);
}

/**
* @dev Destroys `tokenIds`.
* Approvals are not cleared when tokenIds are burned.
*
* Requirements:
*
* - `tokenIds` must exist.
* - `tokenIds` must be strictly ascending.
* - `burner` must be the owner or approved to burn each of the token.
*
* Emits a {Transfer} event for each token burned.
*/
function _batchBurn(
address burner,
uint256[] memory tokenIds,
bool approvalCheck
) internal virtual {
// We can use unchecked as the length of `tokenIds` is bounded
// to a small number by the max block gas limit.
unchecked {
// The next `tokenId` to be minted (i.e. `_nextTokenId()`).
uint256 stop = _currentIndex;

uint256 n = tokenIds.length;

// For checking if the `tokenIds` are strictly ascending.
uint256 prevTokenId;

uint256 tokenId;
uint256 currTokenId;
uint256 prevOwnershipPacked;
address prevTokenOwner;
uint256 lastOwnershipPacked;
address tokenOwner;
bool mayBurn;
for (uint256 i; i != n; ) {
tokenId = tokenIds[i];

// Revert `tokenId` is out of bounds.
if (_or(tokenId < _startTokenId(), stop <= tokenId)) revert OwnerQueryForNonexistentToken();

// Revert if `tokenIds` is not strictly ascending.
if (i != 0)
if (tokenId <= prevTokenId) revert TokenIdsNotStrictlyAscending();

// Scan backwards for an initialized packed ownership slot.
// ERC721A's invariant guarantees that there will always be an initialized slot as long as
// the start of the backwards scan falls within `[_startTokenId() .. _nextTokenId())`.
for (uint256 j = tokenId; (prevOwnershipPacked = _packedOwnerships[j]) == 0; ) --j;

// If the initialized slot is burned, revert.
if (prevOwnershipPacked & _BITMASK_BURNED != 0) revert OwnerQueryForNonexistentToken();

// Unpack the `tokenOwner` from bits [0..159] of `prevOwnershipPacked`.
tokenOwner = address(uint160(prevOwnershipPacked));

if (tokenOwner != prevTokenOwner) {
// Update `prevTokenOwner`.
prevTokenOwner = tokenOwner;

// Check if the burner is either the owner or an approved operator for all tokens
mayBurn = !approvalCheck || tokenOwner == burner || isApprovedForAll(tokenOwner, burner);
}

currTokenId = tokenId;
uint256 offset;
do {
// Revert if the burner is not authorized to burn the token.
if (!mayBurn)
if (_tokenApprovals[currTokenId].value != burner) revert TransferCallerNotOwnerNorApproved();
// Call the hook.
_beforeTokenTransfers(tokenOwner, address(0), currTokenId, 1);
// Emit the `Transfer` event for burn.
emit Transfer(tokenOwner, address(0), currTokenId);
// Call the hook.
_afterTokenTransfers(tokenOwner, address(0), currTokenId, 1);
// Increment `offset` and update `currTokenId`.
currTokenId = tokenId + (++offset);
} while (
// Neither out of bounds, nor at the end of `tokenIds`.
!_or(currTokenId == stop, i + offset == n) &&
// Token ID is sequential.
tokenIds[i + offset] == currTokenId &&
// The packed ownership slot is not initialized.
(lastOwnershipPacked = _packedOwnerships[currTokenId]) == 0
);

// Update the packed ownership for `tokenId` in ERC721A's storage.
_packedOwnerships[tokenId] =
_BITMASK_BURNED |
(block.timestamp << _BITPOS_START_TIMESTAMP) |
uint256(uint160(tokenOwner));

// If the slot after the mini batch is neither out of bounds, nor initialized.
if (currTokenId != stop)
if (lastOwnershipPacked == 0)
if (_packedOwnerships[currTokenId] == 0)
// If `lastOwnershipPacked == 0` we didn't break the loop due to an initialized slot.
_packedOwnerships[currTokenId] = prevOwnershipPacked;

// Update the address data in ERC721A's storage.
//
// Note that this update has to be in the loop as tokens
// can be burned by an operator that is not the token owner.
_packedAddressData[tokenOwner] += (offset << _BITPOS_NUMBER_BURNED) - offset;

// Advance `i` by `offset`, the number of tokens burned in the mini batch.
i += offset;

// Set the `prevTokenId` for checking that the `tokenIds` is strictly ascending.
prevTokenId = currTokenId - 1;
}
// Increase the `_burnCounter` in ERC721A's storage.
_burnCounter += n;
}
}

// =============================================================
// EXTRA DATA OPERATIONS
// =============================================================
Expand Down Expand Up @@ -1311,4 +1435,13 @@ contract ERC721A is IERC721A {
revert(0x00, 0x04)
}
}

/**
* @dev Branchless or.
*/
function _or(bool a, bool b) private pure returns (bool c) {
assembly {
c := or(a, b)
}
}
}
5 changes: 5 additions & 0 deletions contracts/IERC721A.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ interface IERC721A {
*/
error OwnershipNotInitializedForExtraData();

/**
* The `tokenIds` must be strictly ascending.
*/
error TokenIdsNotStrictlyAscending();

/**
* `_sequentialUpTo()` must be greater than `_startTokenId()`.
*/
Expand Down
19 changes: 19 additions & 0 deletions contracts/extensions/ERC721ABatchBurnable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs

pragma solidity ^0.8.4;

import './ERC721ABurnable.sol';
import './IERC721ABatchBurnable.sol';

/**
* @title ERC721ABatchBurnable.
*
* @dev ERC721A token optimized for batch burns.
*/
abstract contract ERC721ABatchBurnable is ERC721ABurnable, IERC721ABatchBurnable {
function batchBurn(uint256[] memory tokenIds) public virtual override {
_batchBurn(msg.sender, tokenIds, true);
}
}
14 changes: 14 additions & 0 deletions contracts/extensions/IERC721ABatchBurnable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs

pragma solidity ^0.8.4;

import './IERC721ABurnable.sol';

/**
* @dev Interface of ERC721ABatchBurnable.
*/
interface IERC721ABatchBurnable is IERC721ABurnable {
function batchBurn(uint256[] memory tokenIds) external;
}
7 changes: 7 additions & 0 deletions contracts/interfaces/IERC721ABatchBurnable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs

pragma solidity ^0.8.4;

import '../extensions/IERC721ABatchBurnable.sol';
50 changes: 50 additions & 0 deletions contracts/mocks/ERC721ABatchBurnableMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creators: Chiru Labs

pragma solidity ^0.8.4;

import '../extensions/ERC721ABatchBurnable.sol';
import './DirectBurnBitSetterHelper.sol';

contract ERC721ABatchBurnableMock is ERC721ABatchBurnable, DirectBurnBitSetterHelper {
constructor(string memory name_, string memory symbol_) ERC721A(name_, symbol_) {}

function exists(uint256 tokenId) public view returns (bool) {
return _exists(tokenId);
}

function safeMint(address to, uint256 quantity) public {
_safeMint(to, quantity);
}

function getOwnershipAt(uint256 index) public view returns (TokenOwnership memory) {
return _ownershipAt(index);
}

function totalMinted() public view returns (uint256) {
return _totalMinted();
}

function totalBurned() public view returns (uint256) {
return _totalBurned();
}

function numberBurned(address owner) public view returns (uint256) {
return _numberBurned(owner);
}

function initializeOwnershipAt(uint256 index) public {
_initializeOwnershipAt(index);
}

function batchBurnUnoptimized(uint256[] memory tokenIds) public {
unchecked {
uint256 tokenId;
for (uint256 i; i < tokenIds.length; ++i) {
tokenId = tokenIds[i];
_burn(tokenId);
}
}
}
}
Loading