As time moves on and more Solidity devs get their hands dirty with ERC721 and ERC1155 standards, they start paying more attention to how important immutability and on-chain data truly are.
However, one reiterating mistake that keeps being overlooked is the use of data structures intended for other protocols interactions and on-chain discovery.
It is becoming increasingly more common for smart contracts to loop over items in a for-loop during transactions in or at transfer time. Enumerable data structures, however, are meant for on-chain discovery and not frontend consumption. The use cases of data structures such as `EnumerableSet` derive from its utility when it comes to being discovered on-chain by other protocols in a “cheap” way. This means that other protocols can easily and cheaply iterate over an account’s NFTs.
However, at minting time, it becomes more and more expensive to loop over such items. Therefore, the good practice of Enumerable data structures is to use them only for on-chain use cases.
If we think about an account holding a few NFTs and an iteration involving a couple of transactions (remember the `for` loop iteration we mentioned), the chances of that function running out of gas will increase, even for an off-chain read.
If we go back to the basics, even if a function is declared as `virtual`, it will only be a “free read” function whenever items are not being transferred. If a user has, say 10 items, it will become quite expensive to check the list of ids and go through each one of the ids, consequently increasing the number of external calls from the protocol contract.
function useTokenIds(
address registry,
uint256[] calldata tokenIds
) external {
address sender = msg.sender;
uint256 tokenId;
for (uint256 i; i < tokenIds.length; i++){
tokenId = tokenIds[i];
require(
IERC721(registry).ownerOf(tokenIds[i]) == sender,
“not owner of those tokenIds”
);
_doSomethingWithThisTokenId(registry, tokenId, sender)
}
}
If the frontend ever needs that list of ids we can implement a getter function to go through all tokens and return those token ids we are interested in. Doing this we would avoid using data structures like Enumerable because we do not really need other on-chain protocols to know about it.
One could use
EIP721 tells it right in the EIP:
Alternatives considered: remove the asset enumeration function if it requires a for-loop, return a Solidity array type from enumeration functions.