-
Notifications
You must be signed in to change notification settings - Fork 12k
Review of ERC721Consecutive
in v4.8.0-rc.0
#3711
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
Comments
Hello @tinchoabbate You raise a lot of interesting points! I'm going to answer including extra description that you may not need, but that would possibly help other readers.
That being said, we are possibly missing the point. Maybe we should try go be beyond what already available and feature that no-one else provides (AFAIK). It would be a balance against the complexity of the code, and the resulting costs, but I'd be open to have the discussed in a dedicated issue ! Note that the uint96 limitation is indeed hidden, and result for data packing for gas optimizations. IMO, its not an issue because of point 2.
We could use an
If we did not return the value, you'd have a hard time knowing which tokens were minted. It would require an storage read operation that we can avoid. Note that since this is internal, its only designed for dev building features on top of it, not external users querying it. As for the batch mint of size 0, they are no-ops, and our policy is to accept no-ops to avoid disturbing workflows (just like we accept transferFrom of 0 tokens when approval is 0. |
|
|
And Inheritance is a pain for us, because we don't control the linearization order of modules in the users contracts... still we try to do our best
|
|
wow, great answers 🙌 I'll try to summarize the most relevant points here. Everything not included I acknowledged and agreed with you. 2. To different extents, it seems we all agree on making the value customizable and documenting the potential consequences of changing it. This point is already listed in #3712. 3. I wasn't sure whether non-used internal function were dropped or not. That's good to know! 5. I like @frangio's idea to rename hooks to "batch". This point is already listed in #3712. 6. Agreed. Still, note the returned value is not tested nor documented. Seems worth including in #3712. 8. Haha agreed, I wasn't expecting to execute a call for each minted token. Perhaps there's room to do something clever with the additional 9. I re-read the docstrings and tests for the 10. Agreed that it's not a security issue. Still worth changing. I see this is already done in #3712. 14. Agreed. Which makes a stronger argument for testing and documenting the return value of |
Hey folks!
These days I'm reviewing the
v4.8.0-rc.0
release candidate. I'll drop in this issue my concerns and comments and questions about theERC721Consecutive
contract and the related functions in theERC721
contract.I spent only a couple of hours on these, so take these notes just as a contribution from an independent researcher, not as a comprehensive audit 😄 And needless to say, feel free to disregard any of the suggestions here as you find best.
I'll number the notes so that it's easier to reference in comments or other issues or PRs. They're listed in no particular order of priority.
The EIP 2309 is only concerned about the event, and not the creation/transfer of batches of tokens. However, the docstrings for
ERC721Consecutive
state:Implementation of the ERC2309 "Consecutive Transfer Extension" as defined in https://eips.ethereum.org/EIPS/eip-2309
. Which is questionable, because the OZ implementation is adding opinionated features and limitations not indicated by the standard. Two examples:ERC721Consecutive
, the token IDs of minted tokens in batches are limited to auint96
type, which is not indicated by this EIP. If I only consider how the event is defined, where it uses auint256
for the token IDs, I could argue that this implementation deviates from the standard. In any case, my main concern here is that (as far as I have seen) this limitation is not explicitly documented nor tested.ERC721Consecutive
, token IDs of minted tokens are assumed to be consecutive and starting at 0. The former is stated by the EIP (so that's fine), but the latter is not. I'm definitely not familiar with real use-cases for ERC721 implementations, so I wouldn't be appropriate to say what's best here. Just saying that it may be worth thinking whether imposing this on developers is a sensible choice or not.The hardcoded
5000
maximum batch size could be moved to a contract-level constant. Even better, even if you may consider5000
the default size, it could be interesting provide a way for it to be overridden by developers to another number that suits them best.Defining
_beforeConsecutiveTokenTransfer
and_afterConsecutiveTokenTransfer
in theERC721
contract instead of inERC721Consecutive
seems out of place. I understand this is done because_balances
is private, am I correct ? Doesn't this unnecessarily increase deployment costs of anyERC721
contract?When minting a batch using
ERC721Consecutive::_mintConsecutive
, the update of balances (i.e., the write to_balances
) happens within theERC721::_beforeConsecutiveTokenTransfer
hook. This is different to what happens when minting a single token. Because inERC721::_mint
the actual update of balances is done after the internal hookERC721::_beforeTokenTransfer
. This doesn't seem to be a problem on its own. Though I do wonder whether it'd be cleaner ifERC721Consecutive
followed the behavior ofERC721
, which is what developers are already used to.The docstrings for
_beforeConsecutiveTokenTransfer
and_afterConsecutiveTokenTransfer
might be misleading, because "consecutive token transfers" could be understood as two transfers of the same token in the same transaction. I'd try to be more specific here.The
ERC721Consecutive::_mintConsecutive
function returns auint96
type, but:- It's unclear why this is needed. Its counterpart
ERC721::_mint
doesn't return anything. I'd at least try to document the purpose of this returned value.- The returned value is never tested.
- The returned value is called
first
, which does not seem self-explanatory to me.- The
uint96
returned is always set to the first token ID to be minted, even if no tokens are minted whenbatchSize
is zero. Could this be confusing for a caller contract ? Why not simply revert ifbatchSize
is zero ?The
ERC721Consecutive::_mintConsecutive
function can only mint tokens to a non-zero address. Therefore, it seems that inERC721::_beforeConsecutiveTokenTransfer
thefrom != address(0)
andto != address(0)
checks are not necessary. Perhaps those check are there for future extensions ? Or just as an example ? Not sure. Might be worth documenting why those checks are there.The hook
onERC721Received
on the receiver of a batch minting operation is never called. The EIP says nothing about it, so it's definitely up to the implementation. Still I'd at least comment that you've decided not to include it, and people should ensure the receiver of a batch mint can handle the tokens.According to this contract, batch minting can only happen during construction. In an upgradeable setting, this means that a reinitialization of the proxy would not allow minting tokens in batch. In principle I'd say that's fair, because the docs are already saying that minting can only happen during construction. Which is a whole different thing than reinitialization of a proxy. I just wonder whether the average developer is fully aware of the distinction. I'm mentioning this because it's unclear whether this is an intended behavior you thought about. In any case, seems worth testing and documenting.
There's an off-by-one error in the
ERC721Consecutive::_afterTokenTransfer
function. If I understand correctly,tokenId <= _totalConsecutiveSupply()
should betokenId < _totalConsecutiveSupply()
.I found it interesting that in
ERC721Consecutive::_afterTokenTransfer
, the call tosuper._afterTokenTransfer
happens after the extended logic. I checked other token extensions and the order is different. Example: inERC20Votes::_afterTokenTransfer
, the call tosuper._afterTokenTransfer
happens before the extended logic. Honestly not sure what's best here, because in both cases_afterTokenTransfer
is empty, so it doesn't seem too relevant at first. Do you follow any guidelines for these cases ?There's an unused
SafeCast.sol
import.Two tiny typos. Line 102: "token" should say "tokens". Line 112: "is" should say "in".
Have you considered making
ERC721Consecutive::_totalConsecutiveSupply
internal
instead ofprivate
? It'd be easier for implementation contracts to access the token ID after a batch mint. I found myself having to manually change it tointernal
to run some custom local tests.The text was updated successfully, but these errors were encountered: