OpenZeppelin / openzeppelin-contracts

OpenZeppelin Contracts is a library for secure smart contract development.
https://openzeppelin.com/contracts
MIT License
24.76k stars 11.76k forks source link

ERC721Enumerable - Add back `_tokensOfOwner()` #3106

Closed imsys closed 2 years ago

imsys commented 2 years ago

tokensOfOwner() was first suggest on #1512 and implemented on #1522 and was removed in a bulk edit that makes me wonder if it was removed accidentally. https://github.com/OpenZeppelin/openzeppelin-contracts/commit/bd0778461da82364aebd2d763bb338795c5f9fa7

This function helps when coding web3 apps. Instead of calling the RPC multiple times to get all NFTs owned by the user, it can be done at once by calling tokensOfOwner()

frangio commented 2 years ago

The removal was not accidental, it's explained in https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2160.

Working with arbitrary sized arrays in Solidity is not a good idea, as it's a potential attack vector. I don't think this is something we should add back. What is your use case?

imsys commented 2 years ago

I can see the problem if someone tried to loop over a long array in Solidity, is there any other problem that I may be missing?

The thing is that in a webapp, if the user has 20 or more NFTs, I have to make 20 requests to the RPC to get all the ids. Maybe 20 more two get the metadata, but that I can have previously cached in the server. Isn't it too inefficient to make that many calls to a RPC server? Or am I over thinking/worrying? I know we can use 3rd party APIs like Moralis that keeps a track of all the NFTs a user owns, but I would like to not be dependent on them for this if possible.

And now thinking about it, maybe I could just create a secondary contract with tokensOfOwner() that would loop over tokenOfOwnerByIndex(), and by having a RPC calling it would be a gasless way to get the list of all NFTs owned by the user. 🤔 I will try this.

imsys commented 2 years ago

So, I just tested this, and it works!

I know it is an unbounded loop, but it doesn't seem to be a problem for a RPC.

pragma solidity ^0.8.0;

// SPDX-License-Identifier: MIT

import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";

contract ListNFT {

    function listUserNFTs(address contractAddress, address owner) external view returns (uint[] memory) {

        uint balance = IERC721Enumerable(contractAddress).balanceOf(owner);

        uint[] memory tokens = new uint[](balance);

        for (uint i=0; i<balance; i++) {
            tokens[i] = (IERC721Enumerable(contractAddress).tokenOfOwnerByIndex(owner, i));
        }

        return tokens;
    }
}
frangio commented 2 years ago

it doesn't seem to be a problem for a RPC.

That's right, RPC eth_call allows much more gas than a transaction so it wouldn't be a problem there.

imsys commented 2 years ago

For me this can be closed then, unless someone else has anything else to add.