-
Notifications
You must be signed in to change notification settings - Fork 11.9k
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
Refactor ERC721 to use _update pattern #4419
Conversation
|
Name | Type |
---|---|
openzeppelin-solidity | Major |
Click here to learn what changesets are, and how to add one.
Click here if you're a maintainer who wants to add a changeset to this PR
function _checkApproved(address owner, address spender, uint256 tokenId) internal view virtual { | ||
if (!_isApproved(owner, spender, tokenId)) { | ||
address actualOwner = _ownerOf(tokenId); | ||
if (owner == actualOwner) { | ||
revert ERC721InsufficientApproval(spender, tokenId); | ||
} else { | ||
revert ERC721IncorrectOwner(owner, tokenId, actualOwner); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function checks the approval assuming owner is correct, and if that returns false, it gets the actual owner to throw the error. So the argument is considered correct in one case, and not correct in the other ?
I'm not really confortable with that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mainly did this to get the current tests to pass, I don't know if this is necessary.
owner = ownerOf(tokenId); | ||
if (owner != from) { | ||
revert ERC721IncorrectOwner(from, tokenId, owner); | ||
function _update(address from, address to, uint256 tokenId) internal virtual { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a fan of having both from
and tokenId
in the arguments. when you say which token you want to transfer, it should be clear that its from the current owner. My worry is that devs will want to call that internally, and they'll have to get _ownerOf
, just to pass it to this function, that will then check it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In particular, that is true of burn:
When you want to burn a token, you now have to get the owner using one sload, and then verify that you got the correct one by duplicating this sload.
*/ | ||
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); | ||
error ERC721IncorrectOwner(address claimedOwner, uint256 tokenId, address actualOwner); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this change also be needed in the EIP itself? I'm worried there's a table with definitions there that might need updating.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly. I made this change because I found the existing names confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see how the existing names are confusing, but I'd look for a way in which the naming makes sense for all errors. Would it be clear if we rename sender
completely to from
?
contracts/token/ERC721/ERC721.sol
Outdated
/** | ||
* @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override. | ||
*/ | ||
function _updateBalance(address account, uint128 value) internal virtual { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would push a bit for adding _unsafe*
prefix. The NatSpec itself mentions its unsafe and I'm not sure if the name correctly reflects that. What do you think?
Also, why not _increaseBalance
? The function gives the sense of being a getter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The natspec is outdated. This function should not be described as unsafe. It will never lead to overflow.
I don't understand why updateBalance
would sound like it's a getter. increaseBalance
is good too, but I thought the parallel with _update
made sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why updateBalance would sound like it's a getter.
My bad, I meant it sounds like a setter instead, not a value increase.
increaseBalance is good too, but I thought the parallel with _update made sense.
Makes sense as long as the NatSpec correctly reflects what it does. I changed it for _increaseBalance
because I'm strongly in favor of that name. We can discuss it.
) internal virtual override { | ||
super._beforeTokenTransfer(from, to, firstTokenId, batchSize); | ||
|
||
_requireNotPaused(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's a good moment to use whenNotPaused
instead as suggested here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I prefer the function call. They are functionally equivalent but the function call is visible inside the function, I just find it more explicit. I don't know if everyone feels the same though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer whenNotPaused
precisely because it's less explicit (== less pervasive?).
I'm fine with the function call if we agree to keep consistency between this and ERC1155Pausable where we use the modifier instead.
revert ERC721InsufficientApproval(_msgSender(), tokenId); | ||
} | ||
|
||
function transferFrom(address from, address to, uint256 tokenId) public { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we removing virtual from these function ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same reason for _transfer
but I suppose the public functions have other considerations. Do you think they should remain virtual?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes.
Our guidelines are to make (almost) everything virtual. I'm ok to making _transfer
, _mint
and _burn
non virtual, because they can be bypassed by calling _update
directly. But apart from these 3, everything else should remain virtual IMO.
Closing in favor of #4377. |
A simpler alternative to #4377 using:
_burn
now has afrom
argument.Fixes LIB-628