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

Added burn functionality #61

Merged
merged 41 commits into from
Feb 12, 2022
Merged

Conversation

Vectorized
Copy link
Collaborator

@Vectorized Vectorized commented Feb 1, 2022

  • An internal _burn function which behaves like the OpenZeppelin's ERC721 implementation.
    • Burns to the zero address.
    • Decreases totalSupply.
    • Relevant functions and extensions updated to support burn function.
  • An public burn function which behaves like the OpenZeppelin's ERC721Burnable implementation.
    • Moved to a separate extension extensions/ERC721ABurnable.sol
  • Tests for ERC721ABurnable and ERC721ABurnable + ERC721AOwnersExplicit.
  • Changed AddressData values from uint128 to uint64 to pack more information per SSTORE.
    • It is extremely unlikely that a typical NFT project can use 2**64-1 slots.
    • Added numberBurned.

This implementation is designed for future extensibility, for example:

  • An additional aux variable under AddressData to store miscellaneous variable(s) to allow users to make use of the remaining 64-bits
    (e.g. to store number of pre-sale mints for each address).

contracts/ERC721A.sol Outdated Show resolved Hide resolved
contracts/ERC721A.sol Outdated Show resolved Hide resolved
@Vectorized Vectorized changed the title Added burn functionality. Added burn functionality Feb 1, 2022
@locationtba
Copy link

was just about to start this PR good thing i checked first lol

contracts/ERC721A.sol Outdated Show resolved Hide resolved
contracts/ERC721A.sol Outdated Show resolved Hide resolved
contracts/ERC721A.sol Outdated Show resolved Hide resolved
contracts/ERC721A.sol Outdated Show resolved Hide resolved
Copy link

@locationtba locationtba left a comment

Choose a reason for hiding this comment

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

thanks for putting this together, def a common ask for burn. think we could squeeze a bit more optimization out of this

contracts/ERC721A.sol Outdated Show resolved Hide resolved
@Vectorized Vectorized changed the title Added burn functionality Added burn functionality + optional id starting from 1 Feb 2, 2022
@Vectorized Vectorized changed the title Added burn functionality + optional id starting from 1 Added burn functionality + optional id starting from 1 via burn Feb 2, 2022
@Vectorized Vectorized changed the title Added burn functionality + optional id starting from 1 via burn Added burn functionality + optional id starting from 1 Feb 2, 2022
@Vectorized Vectorized changed the title Added burn functionality + optional id starting from 1 Added burn functionality Feb 2, 2022
@Vectorized
Copy link
Collaborator Author

Added the optimizations from 1129166

Copy link
Collaborator

@cygaar cygaar left a comment

Choose a reason for hiding this comment

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

Try to keep the gas optimizations we added in #59 - mostly just need to remove the initializations to 0.

contracts/ERC721A.sol Outdated Show resolved Hide resolved
contracts/ERC721A.sol Outdated Show resolved Hide resolved
contracts/ERC721A.sol Outdated Show resolved Hide resolved
contracts/ERC721A.sol Outdated Show resolved Hide resolved
contracts/ERC721A.sol Outdated Show resolved Hide resolved
contracts/ERC721A.sol Outdated Show resolved Hide resolved
test/extensions/ERC721ABurnable.test.js Outdated Show resolved Hide resolved
contracts/ERC721A.sol Outdated Show resolved Hide resolved
@Vectorized
Copy link
Collaborator Author

Vectorized commented Feb 9, 2022

Added #84, #85, #87.

#84 _currentIndex instead of _currentindex.
#85 revert() instead of assert(false) to remove compilation warning.

@cygaar

@cygaar
Copy link
Collaborator

cygaar commented Feb 10, 2022

@Vectorized sorry for the delay I've been trying to clear out these smaller PRs to shorten the backlog, will look through this PR tomorrow

Copy link

@locationtba locationtba left a comment

Choose a reason for hiding this comment

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

looks pretty awesome! just some small nits, thanks for pushing this froward

Comment on lines 68 to 70
uint128 internal _currentIndex;

uint128 internal _burnCounter;

Choose a reason for hiding this comment

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

can we put this in a struct?

Copy link
Collaborator Author

@Vectorized Vectorized Feb 10, 2022

Choose a reason for hiding this comment

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

I’d suggest simply leaving a comment stating that the compiler will pack the variables into a single 256 bit word.

Otherwise we have the great burden of coming up with another meaningful variable name and struct name.

Comment on lines +206 to +209
if (!ownership.burned) {
if (ownership.addr != address(0)) {
return ownership;
}

Choose a reason for hiding this comment

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

If we generally don't expect too many burns, it might be more optimal to check burned after L207

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Burn check has to be done for all cases.

  • Start of search is burned. (revert)
  • Start of search has address and not burned. (early return ownership)
  • Start of search has no address and not burned. (backward search)

updatedIndex++;
}

_currentIndex = updatedIndex;
if (updatedIndex > type(uint128).max) revert SafecastOverflow();

Choose a reason for hiding this comment

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

we make assumptions a lot that we won't encounter overflow, is there a reason we're explicitly checking for it here? If we do want to check i would prefer a separate PR that does similar checks everywhere and allows for discussion

Copy link
Contributor

@ahbanavi ahbanavi Feb 10, 2022

Choose a reason for hiding this comment

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

I think the same, also I think all the changes in the _mint function should be in another PR if they are irrelevant to burn functionality.
Edit:
I see now it's for changing _currentIndex type from unit256 to uint128.
Think we should keep using unit256 in this PR for both _currentIndex and _burnCounter and after merge, we open another PR to discuss changing the types. It's out of burn functionality context.

Copy link
Collaborator Author

@Vectorized Vectorized Feb 11, 2022

Choose a reason for hiding this comment

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

I think we can remove the overflow check.

Most if not all users won't need the check. Even if the startIndex can be customized (in the future), most users will choose either 0 or 1, so overflow will still be very unrealistic. I think leaving a cautionary comment will suffice in that case.

As for packing the _currentIndex and _burnCounter, I'm kinda leaning towards including it in this PR.
It was suggested by @locationtba in the spirit of ensuring that the added functionality is gas efficient too.

Copy link
Collaborator

@cygaar cygaar left a comment

Choose a reason for hiding this comment

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

Just a couple small notes on tests and then I'm ready to approve

}
});

it('with token after previously burnt token transfered and burned', async function () {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you add a test to validate that transferring a burnt token gets reverted?

// If the ownership slot of tokenId+1 is not explicitly set, that means the burn initiator owns it.
// Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls.
uint256 nextTokenId = tokenId + 1;
if (_ownerships[nextTokenId].addr == 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.

Could be worth making this a helper function and re-use it here and in the _transfer function, but I don't have strong opinions here since you only have it in two places

this.addr1 = addr1;
this.addr2 = addr2;
await this.token['safeMint(address,uint256)'](this.addr1.address, 10);
await this.token.connect(this.addr1).burn(5);
Copy link
Collaborator

Choose a reason for hiding this comment

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

can you make 5 here a constant called burnedTokenId and use that in the tests below to make it clear what you're testing for? I want to avoid magic numbers where we can

@cygaar
Copy link
Collaborator

cygaar commented Feb 12, 2022

I also merged your other PR in, so you'll need to rebase

Comment on lines +25 to +29
bool isApprovedOrOwner = (_msgSender() == prevOwnership.addr ||
isApprovedForAll(prevOwnership.addr, _msgSender()) ||
getApproved(tokenId) == _msgSender());

if (!isApprovedOrOwner) revert TransferCallerNotOwnerNorApproved();
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any reason that you check for this here instead of internal _burn function?

Copy link
Collaborator Author

@Vectorized Vectorized Feb 12, 2022

Choose a reason for hiding this comment

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

To be consistent with OpenZeppelin’s implementation.

@ahbanavi ahbanavi mentioned this pull request Feb 12, 2022
@cygaar cygaar merged commit fb22621 into chiru-labs:main Feb 12, 2022
@ahbanavi
Copy link
Contributor

Congrats! 🎉 Kudos to @Vectorized and everyone else who worked on this feature. 🚀

Vectorized added a commit to Vectorized/ERC721A that referenced this pull request Feb 13, 2022
cygaar pushed a commit that referenced this pull request Feb 26, 2022
#66)

* replaces currentIndex with _nextTokenId which starts at 1 instead of 0

* adapts test to new starting index of 1

* makes `ERC721AOwnersExplicit` test work with new starting index

* explicitly sets `nextOwnerToExplicitlySet` to 1 because it's initialised with zero

* remove vscode files

* implements @jpegditials suggestions

* adds tests for `tokenByIndex` and `tokenOfOwnerByIndex`

* prevents tokenId 0 to be an existing token

* adapts `tokenOfOwnerByIndex` for starting token index 1 and making tests for it more elaborate.

* renames variable to make it more clear, it's index by the tokenId

* adds unchecked scopes to save on gas as suggested by @Vectorized

* more gas optimizations by @Vectorized

* implements suggested `_startTokenId` function to override to set starting index

* minor adaptions to work with _startTokenId() properly

* brings back original tests

* creates copy of original test with startTokenId set to 1

* adapts comment to reflect flexibility of the starting tokenId

* adapts solidity version of tests

* Replaces revert messages with new contract errors

* fix merge hiccup

* optimisations to save on gas used during deployment

* Added #61

* Edited comments

* Edited README roadmap

* Removed burn test with one-index mock

* Changed _startTokenId to _currentIndex

* adapting test and mock related to index starting at 1 to make them work with recent changes on `main`

* add _totalMinted function

* add tests for _totalMinted

* removes uint128 cast, simplifies comment and lints file

* rename `ERC721AExplicitOwnershipMock` to `ERC721AOwnersExplicitMock` to be aligned with the naming of the contract

* extend from already mocked ERC721A versions to dry code.

* aligns file naming and contract naming

* creates new ERC721A mock which enables the user to specify the startTokenId dynamically

* converts ERC721A test to a parameterized test to work with other mocks as well.

* extracts Helper contract to it's own file

* parameterises ERC721A Explicit Owner Tests to also test a Version of the Contract with a different start token id.

* makes it more transparent where the startTokenId value is coming from

* creates a ERC721ABurnable Mock with custom startTokenId and uses it in a parameterised test suite testing the burnable extension.

* removes code used during development

* unifies code style across all tests

Co-authored-by: webby1111 <webby1111@hotmail.com>
Co-authored-by: Amirhossein Banavi <ahbanavi@gmail.com>
@Vectorized Vectorized deleted the feature/burnable branch April 12, 2022 13:52
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.

5 participants