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

Optimize ERC721 (depends on #1030) #1031

Closed

Conversation

abandeali1
Copy link
Contributor

This PR depends on #1030 (happy to drop that commit if it's an issue).

🚀 Description

There are a ton of redundant checks and state reads in the current ERC721 implementations. I'm assuming that a lot of this is because everything is broken up into multiple function calls. Even though they read from a lot of the same state, the optimizer has trouble combining the SLOADs because they are occurring in different scopes.

By flattening all of the function calls in transferFrom, quick tests show that we save a whopping ~8000 gas per transfer (this is in addition to the ~2000 gas saved by dropping the modifiers in #1030).

IMO, flattening the functions also makes actual token contracts more readable, as we are able to drop a lot of unused functions. The downside is that there is now a lot of redundant code in the mock contracts, but I think the tradeoff is massively worth it.

  • 📘 I've reviewed the OpenZeppelin Contributor Guidelines
  • ✅ I've added tests where applicable to test my new functionality.
  • 📖 I've made sure that my contracts are well-documented.
  • 🎨 I've run the JS/Solidity linters and fixed any issues (npm run lint:all:fix).

@abandeali1 abandeali1 force-pushed the fix/optimize-erc721 branch 2 times, most recently from 759247c to d3634eb Compare June 20, 2018 06:23
uint256 lastToken = ownedTokens[_from][lastTokenIndex];

ownedTokens[_from][removeTokenIndex] = lastToken;
ownedTokens[_from][lastTokenIndex] = 0;

Choose a reason for hiding this comment

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

As per #977 this line should be safe to remove and save a bit more gas.


if (tokenApprovals[_tokenId] != address(0)) {
tokenApprovals[_tokenId] = address(0);
emit Approval(owner, address(0), _tokenId);

Choose a reason for hiding this comment

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

This line should get removed too now that #1039 has been merged.

@johnhforrest
Copy link

Huge fan of this PR. Really helps from a readability perspective and reduces the coupling on the burn/mint logic. Great work.

@abandeali1
Copy link
Contributor Author

@johnhforrest I addressed your comments and rebased. Thanks for reviewing!

Copy link

@johnhforrest johnhforrest left a comment

Choose a reason for hiding this comment

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

LGTM!

@shrugs shrugs added the next label Jul 3, 2018
@mg6maciej
Copy link
Contributor

Why did you remove _mint (and _burn)? It's such a useful function that is exposed in derived contracts after applying some verification logic.

@abandeali1
Copy link
Contributor Author

@mg6maciej I didn't want to leave in any unused functions just for the sake of using them in derived contracts.

If you're asking why they were removed in the first place, see the PR description. The gist of it is that they are horribly inefficient.

@mg6maciej
Copy link
Contributor

I wouldn't want to force users to understand the internals of ERC721 implementation to use it. If you try to derive from this class, you will quickly see, why it's better to have _mint there.
Other implementations here, here provide it as well. If you search github for OpenZeppelin imports, you will see people expect it to be there: here, here, here, here, here, here, here, here...

@abandeali1
Copy link
Contributor Author

Why not just split these functions out into a separate contract? I think they are useful, but I just don't think they should be used internally within transferFrom. And it feels wasteful to increase the cost of deployment for anyone not using these functions.

Happy to add this if it is a blocker.

@abandeali1 abandeali1 force-pushed the fix/optimize-erc721 branch from 6b2d11b to 2db5bce Compare July 5, 2018 16:47
@frangio frangio added this to the v1.12.0 milestone Jul 20, 2018
@nventuro nventuro added kind:improvement contracts Smart contract code. labels Jul 23, 2018
@shrugs shrugs modified the milestones: v1.12.0, v2.0 Aug 2, 2018
@nventuro nventuro removed the next label Aug 24, 2018
@frangio frangio modified the milestones: v2.0, v2.1 Aug 29, 2018
@frangio frangio modified the milestones: Test helpers, v2.1 Nov 20, 2018
@nventuro
Copy link
Contributor

nventuro commented Dec 4, 2018

Hello @abandeali1, thank you for your contribution!

I dug a bit deeper into what was causing the increased gas cost (since it seemed too high for it to come from just a couple extra SLOADs), and found that the reason lies behind the _removeTokenFrom and _addTokenTo calls: the compiler isn't smart enough to realize there's no need to delete the token from the original user because it can simply be re-assigned.

This can be more easily fixed by adding a function to handle the special case of transferring (since add and remove need to continue existing to support _mint and _burn): I've tested this and it achieves similar gas improvements (there is a total gas cost difference of less than 1% between this and your approach).

Because that's a less disruptive change (we do want _mint and _burn), and this PR got outdated after the 2.0 release (sorry about the delay!), I'm closing this in favor of #1539. Once again, thanks to @abandeali1 for pushing for this, and to everyone who participated in the discussion and validated this idea!

@nventuro nventuro closed this Dec 4, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contracts Smart contract code.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants