I was wondering why the ERC721Enumerable smart contract only exposes a tokenOfOwnerByIndex which returns only one of the owned token and doesn’t expose a tokensOfOwner which would return _ownedTokens[owner]. I guess there is a good reason for that but can you shed some light?
I was maybe thinking exposing a tokensOfOwner method in our smart contract.
/**
* @dev Gets the list of token IDs of the requested owner.
* @param owner address owning the tokens
* @return uint256[] List of token IDs owned by the requested address
*/
function _tokensOfOwner(address owner) internal view returns (uint256[] storage) {
return _ownedTokens[owner];
}
The reason behind this is that returning an array is a bad idea, since that will eventually stop working once the array works. Read the discussion here for more information regarding the issue and alternatives.
@nventuro Based on the PR comment that returning an array doesn’t scale, should ERC721Enumerable.sol have the internal getter _tokensOfOwner at all or have some comments on it’s use?
Hm, perhaps we should add a comment briefly explaining this. The function is ‘safe’ to use from Solidity (as safe as any array is), the core issue described in that PR is sending arrays over the RPC interface.
@piedup pagination is a great idea! It has been discussed multiple times, and returning a slice of the array by passing two indexes (start and end) is IMO the most flexible way to handle this, while still retaining performance.
It would be great to get @nventuro’s thoughts (especially on my feedback )
I don’t know if it would be better to use the internal function _tokensOfOwner to get all the owners tokens and filter on that, rather than calling the public function tokenOfOwnerByIndex for each token which has a require check?
I was wondering if returning an empty array might be better than reverting for zero page or row parameters?
Perhaps using index rather than i?
I am getting used to the OpenZeppelin code style e.g. Parameters must not be prefixed with an underscore. It is worth having a look at if your team don’t yet have a code style guide (plus linting of Solidity if you aren’t already).
OpenZeppelin includes a Math library with a min function that you could use.
I like that implementation! Not sure why you are using 1-bssed indexing though (making page zero invalid): it’d be easier and more consistent to allow page zero and remove the - 1.
@abcoathup comment regarding avoiding having the same statement being executed multiple times makes a lot of sense. There are multiple ways to achieve this: filtering out results over a storage pointer (what _tokensOfOwner returns) could make sense.
I’m thinking it may be worthwhile to go for a more generic approach and add this as a function to the Arrays library, perhaps something like viewPaginated. So you’d be able to do something like:
function tokensOfOwner(address owner, uint256 page, uin256 pageSize) public view returns (uint256[] memory) {
return _tokensOfOwner(owner).viewPaginated(page, pageSize);
}
The issue with this this is, once again, generics: we’d have to implement viewPaginated for uint256, address, etc., since there’s no language support for such a thing. This has been holding us back for too long IMO, we could get started on some custom templating that auto-generates these as required. cc @frangio
Pagination is something I've wanted to see in smart contracts for a long time!! It makes a lot of sense if you think of smart contracts as queries into a database.
It would be awesome to have a generic pagination mechanism, but unfortunately not possible with today's Solidity.