-
Notifications
You must be signed in to change notification settings - Fork 522
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 queryable extension to ERC721Base #560
Merged
nkrishang
merged 6 commits into
thirdweb-dev:main
from
WhiteOakKong:erc721base-add-queryable
Nov 20, 2023
Merged
Changes from 5 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
16c57fb
add queryable to erc721base
WhiteOakKong 098cee8
clean/lint
WhiteOakKong 9cb5695
uncomment natspec
WhiteOakKong 0536bd3
use erc721Avirtualapprove
WhiteOakKong 125c8c0
Merge branch 'main' into erc721base-add-queryable
WhiteOakKong 105e734
Merge branch 'main' into erc721base-add-queryable
nkrishang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
// SPDX-License-Identifier: MIT | ||
// ERC721A Contracts v3.3.0 | ||
// Creator: Chiru Labs | ||
|
||
pragma solidity ^0.8.4; | ||
|
||
import "./IERC721AQueryable.sol"; | ||
import "../ERC721AVirtualApprove.sol"; | ||
|
||
/** | ||
* @title ERC721A Queryable | ||
* @dev ERC721A subclass with convenience query functions. | ||
*/ | ||
abstract contract ERC721AQueryable is ERC721A, IERC721AQueryable { | ||
/** | ||
* @dev Returns the `TokenOwnership` struct at `tokenId` without reverting. | ||
* | ||
* If the `tokenId` is out of bounds: | ||
* - `addr` = `address(0)` | ||
* - `startTimestamp` = `0` | ||
* - `burned` = `false` | ||
* | ||
* If the `tokenId` is burned: | ||
* - `addr` = `<Address of owner before token was burned>` | ||
* - `startTimestamp` = `<Timestamp when token was burned>` | ||
* - `burned = `true` | ||
* | ||
* Otherwise: | ||
* - `addr` = `<Address of owner>` | ||
* - `startTimestamp` = `<Timestamp of start of ownership>` | ||
* - `burned = `false` | ||
*/ | ||
function explicitOwnershipOf(uint256 tokenId) public view override returns (TokenOwnership memory) { | ||
TokenOwnership memory ownership; | ||
|
||
if (tokenId < _startTokenId() || tokenId >= _currentIndex) { | ||
return ownership; | ||
} | ||
ownership = _ownerships[tokenId]; | ||
if (ownership.burned) { | ||
return ownership; | ||
} | ||
return _ownershipOf(tokenId); | ||
} | ||
|
||
/** | ||
* @dev Returns an array of `TokenOwnership` structs at `tokenIds` in order. | ||
* See {ERC721AQueryable-explicitOwnershipOf} | ||
*/ | ||
function explicitOwnershipsOf(uint256[] memory tokenIds) external view override returns (TokenOwnership[] memory) { | ||
unchecked { | ||
uint256 tokenIdsLength = tokenIds.length; | ||
TokenOwnership[] memory ownerships = new TokenOwnership[](tokenIdsLength); | ||
for (uint256 i; i != tokenIdsLength; ++i) { | ||
ownerships[i] = explicitOwnershipOf(tokenIds[i]); | ||
} | ||
return ownerships; | ||
} | ||
} | ||
|
||
/** | ||
* @dev Returns an array of token IDs owned by `owner`, | ||
* in the range [`start`, `stop`) | ||
* (i.e. `start <= tokenId < stop`). | ||
* | ||
* This function allows for tokens to be queried if the collection | ||
* grows too big for a single call of {ERC721AQueryable-tokensOfOwner}. | ||
* | ||
* Requirements: | ||
* | ||
* - `start` < `stop` | ||
*/ | ||
/* solhint-disable*/ | ||
function tokensOfOwnerIn( | ||
address owner, | ||
uint256 start, | ||
uint256 stop | ||
) external view override returns (uint256[] memory) { | ||
unchecked { | ||
if (start >= stop) revert InvalidQueryRange(); | ||
uint256 tokenIdsIdx; | ||
uint256 stopLimit = _currentIndex; | ||
// Set `start = max(start, _startTokenId())`. | ||
if (start < _startTokenId()) { | ||
start = _startTokenId(); | ||
} | ||
// Set `stop = min(stop, _currentIndex)`. | ||
if (stop > stopLimit) { | ||
stop = stopLimit; | ||
} | ||
uint256 tokenIdsMaxLength = balanceOf(owner); | ||
// Set `tokenIdsMaxLength = min(balanceOf(owner), stop - start)`, | ||
// to cater for cases where `balanceOf(owner)` is too big. | ||
if (start < stop) { | ||
uint256 rangeLength = stop - start; | ||
if (rangeLength < tokenIdsMaxLength) { | ||
tokenIdsMaxLength = rangeLength; | ||
} | ||
} else { | ||
tokenIdsMaxLength = 0; | ||
} | ||
uint256[] memory tokenIds = new uint256[](tokenIdsMaxLength); | ||
if (tokenIdsMaxLength == 0) { | ||
return tokenIds; | ||
} | ||
// We need to call `explicitOwnershipOf(start)`, | ||
// because the slot at `start` may not be initialized. | ||
TokenOwnership memory ownership = explicitOwnershipOf(start); | ||
address currOwnershipAddr; | ||
// If the starting slot exists (i.e. not burned), initialize `currOwnershipAddr`. | ||
// `ownership.address` will not be zero, as `start` is clamped to the valid token ID range. | ||
if (!ownership.burned) { | ||
currOwnershipAddr = ownership.addr; | ||
} | ||
for (uint256 i = start; i != stop && tokenIdsIdx != tokenIdsMaxLength; ++i) { | ||
ownership = _ownerships[i]; | ||
if (ownership.burned) { | ||
continue; | ||
} | ||
if (ownership.addr != address(0)) { | ||
currOwnershipAddr = ownership.addr; | ||
} | ||
if (currOwnershipAddr == owner) { | ||
tokenIds[tokenIdsIdx++] = i; | ||
} | ||
} | ||
// Downsize the array to fit. | ||
assembly { | ||
mstore(tokenIds, tokenIdsIdx) | ||
} | ||
return tokenIds; | ||
} | ||
} | ||
|
||
|
||
/* solhint-enable */ | ||
|
||
/** | ||
* @dev Returns an array of token IDs owned by `owner`. | ||
* | ||
* This function scans the ownership mapping and is O(totalSupply) in complexity. | ||
* It is meant to be called off-chain. | ||
* | ||
* See {ERC721AQueryable-tokensOfOwnerIn} for splitting the scan into | ||
* multiple smaller scans if the collection is large enough to cause | ||
* an out-of-gas error (10K pfp collections should be fine). | ||
*/ | ||
function tokensOfOwner(address owner) external view override returns (uint256[] memory) { | ||
unchecked { | ||
uint256 tokenIdsIdx; | ||
|
||
address currOwnershipAddr; | ||
|
||
uint256 tokenIdsLength = balanceOf(owner); | ||
uint256[] memory tokenIds = new uint256[](tokenIdsLength); | ||
TokenOwnership memory ownership; | ||
for (uint256 i = _startTokenId(); tokenIdsIdx != tokenIdsLength; ++i) { | ||
ownership = _ownerships[i]; | ||
if (ownership.burned) { | ||
continue; | ||
} | ||
if (ownership.addr != address(0)) { | ||
currOwnershipAddr = ownership.addr; | ||
} | ||
if (currOwnershipAddr == owner) { | ||
tokenIds[tokenIdsIdx++] = i; | ||
} | ||
} | ||
return tokenIds; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// SPDX-License-Identifier: MIT | ||
// ERC721A Contracts v3.3.0 | ||
// Creator: Chiru Labs | ||
|
||
pragma solidity ^0.8.4; | ||
|
||
import "../interface/IERC721A.sol"; | ||
|
||
/** | ||
* @dev Interface of an ERC721AQueryable compliant contract. | ||
*/ | ||
interface IERC721AQueryable is IERC721A { | ||
/** | ||
* Invalid query range (`start` >= `stop`). | ||
*/ | ||
error InvalidQueryRange(); | ||
|
||
/** | ||
* @dev Returns the `TokenOwnership` struct at `tokenId` without reverting. | ||
* | ||
* If the `tokenId` is out of bounds: | ||
* - `addr` = `address(0)` | ||
* - `startTimestamp` = `0` | ||
* - `burned` = `false` | ||
* | ||
* If the `tokenId` is burned: | ||
* - `addr` = `<Address of owner before token was burned>` | ||
* - `startTimestamp` = `<Timestamp when token was burned>` | ||
* - `burned = `true` | ||
* | ||
* Otherwise: | ||
* - `addr` = `<Address of owner>` | ||
* - `startTimestamp` = `<Timestamp of start of ownership>` | ||
* - `burned = `false` | ||
*/ | ||
function explicitOwnershipOf(uint256 tokenId) external view returns (TokenOwnership memory); | ||
|
||
/** | ||
* @dev Returns an array of `TokenOwnership` structs at `tokenIds` in order. | ||
* See {ERC721AQueryable-explicitOwnershipOf} | ||
*/ | ||
function explicitOwnershipsOf(uint256[] memory tokenIds) external view returns (TokenOwnership[] memory); | ||
|
||
/** | ||
* @dev Returns an array of token IDs owned by `owner`, | ||
* in the range [`start`, `stop`) | ||
* (i.e. `start <= tokenId < stop`). | ||
* | ||
* This function allows for tokens to be queried if the collection | ||
* grows too big for a single call of {ERC721AQueryable-tokensOfOwner}. | ||
* | ||
* Requirements: | ||
* | ||
* - `start` < `stop` | ||
*/ | ||
function tokensOfOwnerIn( | ||
address owner, | ||
uint256 start, | ||
uint256 stop | ||
) external view returns (uint256[] memory); | ||
|
||
/** | ||
* @dev Returns an array of token IDs owned by `owner`. | ||
* | ||
* This function scans the ownership mapping and is O(totalSupply) in complexity. | ||
* It is meant to be called off-chain. | ||
* | ||
* See {ERC721AQueryable-tokensOfOwnerIn} for splitting the scan into | ||
* multiple smaller scans if the collection is large enough to cause | ||
* an out-of-gas error (10K pfp collections should be fine). | ||
*/ | ||
function tokensOfOwner(address owner) external view returns (uint256[] memory); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't we already have this file somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There isn't a non-upgradable version currently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha