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

Remove ERC721 tokenTransfer hooks #3893

Conversation

JulissaDantes
Copy link
Contributor

Fixes #3535
Replaces #3636

The current way to customize token transfers(mint, burn, transfer) is by overriding the hooks _beforeTokenTransfer and _afterTokenTransfer. This refactor removes both hooks from the ERC721 contract, and implements the logic inside the new _update internal function, so future customizations only rely on overriding a single entry point.

PR Checklist

  • Tests
  • Documentation
  • Changelog entry

@frangio frangio requested a review from Amxx January 5, 2023 22:20
@Amxx
Copy link
Collaborator

Amxx commented Jan 6, 2023

Overall I feel uncomfortable with this change.

Devs that call _update(from, to, tokenId, batchSize) will expect the batch to be transferred, including ownership transfers and events.

The truth is, only batchsize == 1 does the transfer, and any other value is just a hack to update the balances. I think this is very error prone and is not aligned with our guidelines!

@frangio
Copy link
Contributor

frangio commented Jan 6, 2023

@Amxx What alternative do you recommend? We can revisit #3744, but I would like to consider other options.

@Amxx
Copy link
Collaborator

Amxx commented Jan 6, 2023

My first idea would be to remove the batchSize from the _update function, and manage the balances change of batch minting in a different way.

@frangio
Copy link
Contributor

frangio commented Jan 7, 2023

But how? And we should keep in mind that the goal is to minimize overrides for the end user.

@frangio
Copy link
Contributor

frangio commented Jan 9, 2023

We might have to consider adding _mintConsecutive by default in ERC721. It stretches the limits of what's comfortably possible with inheritance.

@Amxx
Copy link
Collaborator

Amxx commented Jan 10, 2023

We might have to consider adding _mintConsecutive by default in ERC721. It stretches the limits of what's comfortably possible with inheritance.

That is one option

There was another option that used overflows, but it used 2 slots per account ... and feels very "hacky"

My concern is that putting _mintConsecutive by default in ERC721 will add gas cost to usecases that don't do any concecutive minting.

Copy link
Collaborator

@Amxx Amxx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not for this PR, but we will have to worry about these extensions.

delete _tokenURIs[tokenId];
function _update(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual override {
super._update(from, to, firstTokenId, batchSize);
if (to == address(0)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if to == address(0) and batchSize > 1, we should probably do something. Revert?

_resetTokenRoyalty(tokenId);
function _update(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual override {
super._update(from, to, firstTokenId, batchSize);
if (to == address(0)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if to == address(0) and batchSize > 1, we should probably do something. Revert?

// After construction, {_mintConsecutive} is no longer available and {_mint} becomes available.
require(Address.isContract(address(this)), "ERC721Consecutive: can't mint during construction");
}
super._update(from, to, firstTokenId, batchSize);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also realized that here we are not following the rule that overrides should do their own stuff before calling super (and not after). We need to add comments documenting why we are doing so.

) internal virtual override {
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);

function _update(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual override {
if (batchSize > 1) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to consider batchSize == 0

@JulissaDantes JulissaDantes force-pushed the refactor/erc721-mint-burn-transfer branch from 7383061 to 769c6ec Compare January 12, 2023 19:29
@JulissaDantes JulissaDantes requested review from frangio and Amxx January 12, 2023 19:29
@Amxx
Copy link
Collaborator

Amxx commented Jan 13, 2023

Here is a summary of the identified issues that would need to be fixed:

  • _update does not check the from. This means that a dev could call the _update function with an invalid from, and cause:
    • an invalid event to be emitted
    • corruption of the balances mapping.
  • _update does different operation depending on the batchSize:
    • if the batchSize is 1, it will update the ownership, reset the approval, update the balances, and emit an event.
    • if the batchSize is not 0, it will just update the balance, but not do the transfer.
      This is error prone, because the dev have to understand that this function will do two very different thinks depending on this "small" parameter. It is possible that devs call this with a batchsize > 1 hoping that it will transfer a batch of token. This argues against having a single _update for both single and batched operations.
  • Cleanly support batchSize = 0 in extensions.
    • Some of them are not designed to handle batches, and just don't know not to process burns of size > 1. The fact that they "hook" into _update(address,address,uint256,uint256) means they could see signals they don't know how to handle. Hopefully they never have to, but this also argues against having a single _update for both single and batched operations.
    • Affected extensions:
      • ERC721Storage
      • ERC721Royalty
      • ERC721Enumerable
  • Document situations where we don't follow our own guidelines.
    • In ERC721Consecutive, we do some operations before, and other after the super._uodate(...) call. This is in conflict with our guidelines

@frangio
Copy link
Contributor

frangio commented Jan 24, 2023

Closing this PR in favor of the issue, where I shared the above summary: #3535

@frangio frangio closed this Jan 24, 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