You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
237 lines
9.1 KiB
Solidity
237 lines
9.1 KiB
Solidity
2 years ago
|
// SPDX-License-Identifier: MIT
|
||
|
// ERC721A Contracts v4.2.3
|
||
|
// Creator: Chiru Labs
|
||
|
|
||
|
pragma solidity ^0.8.4;
|
||
|
|
||
|
import './IERC721AQueryable.sol';
|
||
|
import '../ERC721A.sol';
|
||
|
|
||
|
/**
|
||
|
* @title ERC721AQueryable.
|
||
|
*
|
||
|
* @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`
|
||
|
* - `extraData = 0`
|
||
|
*
|
||
|
* If the `tokenId` is burned:
|
||
|
*
|
||
|
* - `addr = <Address of owner before token was burned>`
|
||
|
* - `startTimestamp = <Timestamp when token was burned>`
|
||
|
* - `burned = true`
|
||
|
* - `extraData = <Extra data when token was burned>`
|
||
|
*
|
||
|
* Otherwise:
|
||
|
*
|
||
|
* - `addr = <Address of owner>`
|
||
|
* - `startTimestamp = <Timestamp of start of ownership>`
|
||
|
* - `burned = false`
|
||
|
* - `extraData = <Extra data at start of ownership>`
|
||
|
*/
|
||
|
function explicitOwnershipOf(uint256 tokenId)
|
||
|
public
|
||
|
view
|
||
|
virtual
|
||
|
override
|
||
|
returns (TokenOwnership memory ownership)
|
||
|
{
|
||
|
if (tokenId >= _startTokenId()) {
|
||
|
if (tokenId < _nextTokenId()) {
|
||
|
ownership = _ownershipAt(tokenId);
|
||
|
if (!ownership.burned) {
|
||
|
ownership = _ownershipOf(tokenId);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @dev Returns an array of `TokenOwnership` structs at `tokenIds` in order.
|
||
|
* See {ERC721AQueryable-explicitOwnershipOf}
|
||
|
*/
|
||
|
function explicitOwnershipsOf(uint256[] calldata tokenIds)
|
||
|
external
|
||
|
view
|
||
|
virtual
|
||
|
override
|
||
|
returns (TokenOwnership[] memory)
|
||
|
{
|
||
|
TokenOwnership[] memory ownerships;
|
||
|
uint256 i = tokenIds.length;
|
||
|
assembly {
|
||
|
// Grab the free memory pointer.
|
||
|
ownerships := mload(0x40)
|
||
|
// Store the length.
|
||
|
mstore(ownerships, i)
|
||
|
// Allocate one word for the length,
|
||
|
// `tokenIds.length` words for the pointers.
|
||
|
i := shl(5, i) // Multiply `i` by 32.
|
||
|
mstore(0x40, add(add(ownerships, 0x20), i))
|
||
|
}
|
||
|
while (i != 0) {
|
||
|
uint256 tokenId;
|
||
|
assembly {
|
||
|
i := sub(i, 0x20)
|
||
|
tokenId := calldataload(add(tokenIds.offset, i))
|
||
|
}
|
||
|
TokenOwnership memory ownership = explicitOwnershipOf(tokenId);
|
||
|
assembly {
|
||
|
// Store the pointer of `ownership` in the `ownerships` array.
|
||
|
mstore(add(add(ownerships, 0x20), i), ownership)
|
||
|
}
|
||
|
}
|
||
|
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`
|
||
|
*/
|
||
|
function tokensOfOwnerIn(
|
||
|
address owner,
|
||
|
uint256 start,
|
||
|
uint256 stop
|
||
|
) external view virtual override returns (uint256[] memory) {
|
||
|
unchecked {
|
||
|
if (start >= stop) _revert(InvalidQueryRange.selector);
|
||
|
// Set `start = max(start, _startTokenId())`.
|
||
|
if (start < _startTokenId()) {
|
||
|
start = _startTokenId();
|
||
|
}
|
||
|
uint256 stopLimit = _nextTokenId();
|
||
|
// Set `stop = min(stop, stopLimit)`.
|
||
|
if (stop >= stopLimit) {
|
||
|
stop = stopLimit;
|
||
|
}
|
||
|
uint256[] memory tokenIds;
|
||
|
uint256 tokenIdsMaxLength = balanceOf(owner);
|
||
|
bool startLtStop = start < stop;
|
||
|
assembly {
|
||
|
// Set `tokenIdsMaxLength` to zero if `start` is less than `stop`.
|
||
|
tokenIdsMaxLength := mul(tokenIdsMaxLength, startLtStop)
|
||
|
}
|
||
|
if (tokenIdsMaxLength != 0) {
|
||
|
// Set `tokenIdsMaxLength = min(balanceOf(owner), stop - start)`,
|
||
|
// to cater for cases where `balanceOf(owner)` is too big.
|
||
|
if (stop - start <= tokenIdsMaxLength) {
|
||
|
tokenIdsMaxLength = stop - start;
|
||
|
}
|
||
|
assembly {
|
||
|
// Grab the free memory pointer.
|
||
|
tokenIds := mload(0x40)
|
||
|
// Allocate one word for the length, and `tokenIdsMaxLength` words
|
||
|
// for the data. `shl(5, x)` is equivalent to `mul(32, x)`.
|
||
|
mstore(0x40, add(tokenIds, shl(5, add(tokenIdsMaxLength, 1))))
|
||
|
}
|
||
|
// 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;
|
||
|
}
|
||
|
uint256 tokenIdsIdx;
|
||
|
// Use a do-while, which is slightly more efficient for this case,
|
||
|
// as the array will at least contain one element.
|
||
|
do {
|
||
|
ownership = _ownershipAt(start);
|
||
|
assembly {
|
||
|
// if `ownership.burned == false`.
|
||
|
if iszero(mload(add(ownership, 0x40))) {
|
||
|
// if `ownership.addr != address(0)`.
|
||
|
// The `addr` already has it's upper 96 bits clearned,
|
||
|
// since it is written to memory with regular Solidity.
|
||
|
if mload(ownership) {
|
||
|
currOwnershipAddr := mload(ownership)
|
||
|
}
|
||
|
// if `currOwnershipAddr == owner`.
|
||
|
// The `shl(96, x)` is to make the comparison agnostic to any
|
||
|
// dirty upper 96 bits in `owner`.
|
||
|
if iszero(shl(96, xor(currOwnershipAddr, owner))) {
|
||
|
tokenIdsIdx := add(tokenIdsIdx, 1)
|
||
|
mstore(add(tokenIds, shl(5, tokenIdsIdx)), start)
|
||
|
}
|
||
|
}
|
||
|
start := add(start, 1)
|
||
|
}
|
||
|
} while (!(start == stop || tokenIdsIdx == tokenIdsMaxLength));
|
||
|
// Store the length of the array.
|
||
|
assembly {
|
||
|
mstore(tokenIds, tokenIdsIdx)
|
||
|
}
|
||
|
}
|
||
|
return tokenIds;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @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 collections should be fine).
|
||
|
*/
|
||
|
function tokensOfOwner(address owner) external view virtual override returns (uint256[] memory) {
|
||
|
uint256 tokenIdsLength = balanceOf(owner);
|
||
|
uint256[] memory tokenIds;
|
||
|
assembly {
|
||
|
// Grab the free memory pointer.
|
||
|
tokenIds := mload(0x40)
|
||
|
// Allocate one word for the length, and `tokenIdsMaxLength` words
|
||
|
// for the data. `shl(5, x)` is equivalent to `mul(32, x)`.
|
||
|
mstore(0x40, add(tokenIds, shl(5, add(tokenIdsLength, 1))))
|
||
|
// Store the length of `tokenIds`.
|
||
|
mstore(tokenIds, tokenIdsLength)
|
||
|
}
|
||
|
address currOwnershipAddr;
|
||
|
uint256 tokenIdsIdx;
|
||
|
for (uint256 i = _startTokenId(); tokenIdsIdx != tokenIdsLength; ) {
|
||
|
TokenOwnership memory ownership = _ownershipAt(i);
|
||
|
assembly {
|
||
|
// if `ownership.burned == false`.
|
||
|
if iszero(mload(add(ownership, 0x40))) {
|
||
|
// if `ownership.addr != address(0)`.
|
||
|
// The `addr` already has it's upper 96 bits clearned,
|
||
|
// since it is written to memory with regular Solidity.
|
||
|
if mload(ownership) {
|
||
|
currOwnershipAddr := mload(ownership)
|
||
|
}
|
||
|
// if `currOwnershipAddr == owner`.
|
||
|
// The `shl(96, x)` is to make the comparison agnostic to any
|
||
|
// dirty upper 96 bits in `owner`.
|
||
|
if iszero(shl(96, xor(currOwnershipAddr, owner))) {
|
||
|
tokenIdsIdx := add(tokenIdsIdx, 1)
|
||
|
mstore(add(tokenIds, shl(5, tokenIdsIdx)), i)
|
||
|
}
|
||
|
}
|
||
|
i := add(i, 1)
|
||
|
}
|
||
|
}
|
||
|
return tokenIds;
|
||
|
}
|
||
|
}
|