Skip to content
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

Closed
wants to merge 8 commits into from

Conversation

frangio
Copy link
Contributor

@frangio frangio commented Jul 4, 2023

A simpler alternative to #4377 using:

function _update(address from, address to, uint256 tokenId) internal;

_burn now has a from argument.

Fixes LIB-628

@frangio frangio requested a review from Amxx July 4, 2023 05:01
@changeset-bot
Copy link

changeset-bot bot commented Jul 4, 2023

⚠️ No Changeset found

Latest commit: cdacda1

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes changesets to release 1 package
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

Comment on lines +233 to +241
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);
}
}
Copy link
Collaborator

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.

Copy link
Contributor Author

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 {
Copy link
Collaborator

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.

Copy link
Collaborator

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.

@frangio frangio mentioned this pull request Jul 4, 2023
6 tasks
*/
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
error ERC721IncorrectOwner(address claimedOwner, uint256 tokenId, address actualOwner);
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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?

/**
* @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 {
Copy link
Member

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.

Copy link
Contributor Author

@frangio frangio Jul 7, 2023

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.

Copy link
Member

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.

contracts/token/ERC721/ERC721.sol Show resolved Hide resolved
contracts/token/ERC721/ERC721.sol Show resolved Hide resolved
contracts/token/ERC721/ERC721.sol Show resolved Hide resolved
contracts/token/ERC721/ERC721.sol Show resolved Hide resolved
contracts/token/ERC721/ERC721.sol Show resolved Hide resolved
contracts/token/ERC721/ERC721.sol Outdated Show resolved Hide resolved
) internal virtual override {
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);

_requireNotPaused();
Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Member

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.

contracts/token/ERC721/extensions/ERC721URIStorage.sol Outdated Show resolved Hide resolved
@ernestognw ernestognw marked this pull request as ready for review July 7, 2023 20:41
revert ERC721InsufficientApproval(_msgSender(), tokenId);
}

function transferFrom(address from, address to, uint256 tokenId) public {
Copy link
Collaborator

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 ?

Copy link
Contributor Author

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?

Copy link
Collaborator

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.

@frangio
Copy link
Contributor Author

frangio commented Jul 12, 2023

Closing in favor of #4377.

@frangio frangio closed this Jul 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants