-
Notifications
You must be signed in to change notification settings - Fork 842
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
Conversation
was just about to start this PR good thing i checked first lol |
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.
thanks for putting this together, def a common ask for burn. think we could squeeze a bit more optimization out of this
Added the optimizations from 1129166 |
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.
Try to keep the gas optimizations we added in #59 - mostly just need to remove the initializations to 0.
@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 |
update specs for _beforeTokenTransfers and _afterTokenTransfers hooks
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.
looks pretty awesome! just some small nits, thanks for pushing this froward
contracts/ERC721A.sol
Outdated
uint128 internal _currentIndex; | ||
|
||
uint128 internal _burnCounter; |
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.
can we put this in a struct?
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’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.
if (!ownership.burned) { | ||
if (ownership.addr != address(0)) { | ||
return ownership; | ||
} |
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.
If we generally don't expect too many burns, it might be more optimal to check burned after L207
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.
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)
contracts/ERC721A.sol
Outdated
updatedIndex++; | ||
} | ||
|
||
_currentIndex = updatedIndex; | ||
if (updatedIndex > type(uint128).max) revert SafecastOverflow(); |
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.
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
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 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.
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 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.
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.
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 () { |
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.
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)) { |
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.
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); |
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.
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
I also merged your other PR in, so you'll need to rebase |
bool isApprovedOrOwner = (_msgSender() == prevOwnership.addr || | ||
isApprovedForAll(prevOwnership.addr, _msgSender()) || | ||
getApproved(tokenId) == _msgSender()); | ||
|
||
if (!isApprovedOrOwner) revert TransferCallerNotOwnerNorApproved(); |
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.
Is there any reason that you check for this here instead of internal _burn
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.
To be consistent with OpenZeppelin’s implementation.
Congrats! 🎉 Kudos to @Vectorized and everyone else who worked on this feature. 🚀 |
#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>
_burn
function which behaves like the OpenZeppelin'sERC721
implementation.burn
function which behaves like the OpenZeppelin'sERC721Burnable
implementation.extensions/ERC721ABurnable.sol
ERC721ABurnable
andERC721ABurnable + ERC721AOwnersExplicit
.AddressData
values fromuint128
touint64
to pack more information perSSTORE
.2**64-1
slots.numberBurned
.This implementation is designed for future extensibility, for example:
aux
variable underAddressData
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).