Skip to content

Commit

Permalink
Rebase ERC721Consecutive work on Checkpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
Amxx committed Aug 10, 2022
1 parent e182730 commit 03835b3
Show file tree
Hide file tree
Showing 8 changed files with 435 additions and 2 deletions.
118 changes: 118 additions & 0 deletions contracts/mocks/ERC721ConsecutiveMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../token/ERC721/extensions/ERC721Burnable.sol";
import "../token/ERC721/extensions/ERC721Consecutive.sol";
import "../token/ERC721/extensions/ERC721Enumerable.sol";
import "../token/ERC721/extensions/ERC721Pausable.sol";
import "../token/ERC721/extensions/draft-ERC721Votes.sol";

/* solhint-disable-next-line contract-name-camelcase */
abstract contract __VotesDelegationInConstructor is Votes {
constructor(address[] memory accounts) {
for (uint256 i; i < accounts.length; ++i) {
_delegate(accounts[i], accounts[i]);
}
}
}

/**
* @title ERC721ConsecutiveMock
*/
contract ERC721ConsecutiveMock is
__VotesDelegationInConstructor,
ERC721Burnable,
ERC721Consecutive,
ERC721Enumerable,
ERC721Pausable,
ERC721Votes
{
constructor(
string memory name,
string memory symbol,
address[] memory receivers,
uint96[] memory amounts
)
__VotesDelegationInConstructor(receivers)
ERC721(name, symbol)
ERC721Consecutive(receivers, amounts)
EIP712(name, "1")
{}

function pause() external {
_pause();
}

function unpause() external {
_unpause();
}

function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC721, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}

function exists(uint256 tokenId) public view returns (bool) {
return _exists(tokenId);
}

function mint(address to, uint256 tokenId) public {
_mint(to, tokenId);
}

function safeMint(address to, uint256 tokenId) public {
_safeMint(to, tokenId);
}

function _ownerOf(uint256 tokenId) internal view virtual override(ERC721, ERC721Consecutive) returns (address) {
return super._ownerOf(tokenId);
}

function _burn(uint256 tokenId) internal virtual override(ERC721, ERC721Consecutive) {
super._burn(tokenId);
}

function _mint(address to, uint256 tokenId) internal virtual override(ERC721, ERC721Consecutive) {
super._mint(to, tokenId);
}

function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual override(ERC721, ERC721Enumerable, ERC721Pausable) {
super._beforeTokenTransfer(from, to, tokenId);
}

function _afterTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual override(ERC721, ERC721Votes) {
super._afterTokenTransfer(from, to, tokenId);
}

function _beforeConsecutiveTokenTransfer(
address from,
address to,
uint256 first,
uint256 last
) internal virtual override(ERC721, ERC721Enumerable, ERC721Pausable) {
super._beforeConsecutiveTokenTransfer(from, to, first, last);
}

function _afterConsecutiveTokenTransfer(
address from,
address to,
uint256 first,
uint256 last
) internal virtual override(ERC721, ERC721Votes) {
super._afterConsecutiveTokenTransfer(from, to, first, last);
}
}
35 changes: 33 additions & 2 deletions contracts/token/ERC721/ERC721.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
* @dev See {IERC721-ownerOf}.
*/
function ownerOf(uint256 tokenId) public view virtual override returns (address) {
address owner = _owners[tokenId];
address owner = _ownerOf(tokenId);
require(owner != address(0), "ERC721: invalid token ID");
return owner;
}
Expand Down Expand Up @@ -210,6 +210,13 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
}

/**
* @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
*/
function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
return _owners[tokenId];
}

/**
* @dev Returns whether `tokenId` exists.
*
Expand All @@ -219,7 +226,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
* and stop existing when they are burned (`_burn`).
*/
function _exists(uint256 tokenId) internal view virtual returns (bool) {
return _owners[tokenId] != address(0);
return _ownerOf(tokenId) != address(0);
}

/**
Expand Down Expand Up @@ -452,4 +459,28 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
address to,
uint256 tokenId
) internal virtual {}

/**
* TODO
*/
function _beforeConsecutiveTokenTransfer(
address from,
address to,
uint256 first,
uint256 last
) internal virtual {
if (from != address(0)) {
_balances[from] -= last - first + 1;
}
if (to != address(0)) {
_balances[to] += last - first + 1;
}
}

function _afterConsecutiveTokenTransfer(
address from,
address to,
uint256 first,
uint256 last
) internal virtual {}
}
98 changes: 98 additions & 0 deletions contracts/token/ERC721/extensions/ERC721Consecutive.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/ERC721Burnable.sol)

pragma solidity ^0.8.0;

import "../ERC721.sol";
import "../../../utils/Checkpoints.sol";
import "../../../utils/math/SafeCast.sol";
import "../../../utils/structs/BitMaps.sol";

/**
* @title ERC721 Cheap sequential minting
*/
abstract contract ERC721Consecutive is ERC721 {
using BitMaps for BitMaps.BitMap;
using Checkpoints for Checkpoints.Checkpoint160[];

Checkpoints.Checkpoint160[] private _sequentialOwnership;
BitMaps.BitMap private _sequentialBurn;

event ConsecutiveTransfer(
uint256 indexed fromTokenId,
uint256 toTokenId,
address indexed fromAddress,
address indexed toAddress
);

constructor(address[] memory receivers, uint96[] memory amounts) {
// Check input length
uint256 length = receivers.length;
require(length == amounts.length);

// For each batch of token
for (uint256 i = 0; i < length; ++i) {
_mintConsecutive(receivers[i], amounts[i]);
}
}

function _ownerOf(uint256 tokenId) internal view virtual override returns (address) {
address owner = super._ownerOf(tokenId);

// If token is owned by the core, or beyound consecutive range, return base value
if (owner != address(0) || tokenId > type(uint96).max) {
return owner;
}

// Otherwize, check the token was not burned, and fetch ownership from the anchors
// Note: no need for safe cast, we know that tokenId <= type(uint96).max
return
_sequentialBurn.get(tokenId)
? address(0)
: address(_sequentialOwnership.lowerLookup(uint96(tokenId)));
}

function _mintConsecutive(address to, uint96 batchSize) internal virtual {
require(!Address.isContract(address(this)), "ERC721Consecutive: batch minting restricted to constructor");

require(to != address(0), "ERC721Consecutive: mint to the zero address");
require(batchSize > 0, "ERC721Consecutive: empty batch");
require(batchSize < 5000, "ERC721Consecutive: batches too large for indexing");

uint96 first = _totalConsecutiveSupply();
uint96 last = first + batchSize - 1;

// hook before
_beforeConsecutiveTokenTransfer(address(0), to, first, last);

// push an ownership checkpoint & emit event
_sequentialOwnership.push(SafeCast.toUint96(last), uint160(to));
emit ConsecutiveTransfer(first, last, address(0), to);

// hook after
_afterConsecutiveTokenTransfer(address(0), to, first, last);
}

function _mint(address to, uint256 tokenId) internal virtual override {
// During construction, minting should only be performed using the batch mechanism.
// This is necessary because interleaving mint and batchmint would cause issues.
require(Address.isContract(address(this)), "ERC721Consecutive: cant mint durring construction");

super._mint(to, tokenId);
if (_sequentialBurn.get(tokenId)) {
_sequentialBurn.unset(tokenId);
}
}

function _burn(uint256 tokenId) internal virtual override {
super._burn(tokenId);
if (tokenId <= _totalConsecutiveSupply()) {
_sequentialBurn.set(tokenId);
}
}

function _totalConsecutiveSupply() private view returns (uint96) {
uint256 length = _sequentialOwnership.length;
return length == 0 ? 0 : _sequentialOwnership[length - 1]._key + 1;
}
}
42 changes: 42 additions & 0 deletions contracts/token/ERC721/extensions/ERC721Enumerable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,48 @@ abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {
}
}

/**
* @dev Hook that is called before any batch token transfer. For now this is limited
* to batch minting by the {ERC721Consecutive} extension.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
* transferred to `to`.
* - When `from` is zero, `tokenId` will be minted for `to`.
* - When `to` is zero, ``from``'s `tokenId` will be burned.
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeConsecutiveTokenTransfer(
address from,
address to,
uint256 first,
uint256 last
) internal virtual override {
require(from == address(0) && to != address(0), "ERC721Enumerable: only batch minting is supported");

// Before balance is updated (that is part of the super call)
uint256 length = ERC721.balanceOf(to);

// Do the super call
super._beforeConsecutiveTokenTransfer(from, to, first, last);

// Add to enumerability
for (uint256 tokenId = first; tokenId <= last; ++tokenId) {
// Add to all tokens
_addTokenToAllTokensEnumeration(tokenId);

// Add to owner tokens
_ownedTokens[to][length] = tokenId;
_ownedTokensIndex[tokenId] = length;

++length;
}
}

/**
* @dev Private function to add a token to this extension's ownership-tracking data structures.
* @param to address representing the new owner of the given token ID
Expand Down
11 changes: 11 additions & 0 deletions contracts/token/ERC721/extensions/ERC721Pausable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,15 @@ abstract contract ERC721Pausable is ERC721, Pausable {

require(!paused(), "ERC721Pausable: token transfer while paused");
}

function _beforeConsecutiveTokenTransfer(
address from,
address to,
uint256 first,
uint256 last
) internal virtual override {
super._beforeConsecutiveTokenTransfer(from, to, first, last);

require(!paused(), "ERC721Pausable: token transfer while paused");
}
}
15 changes: 15 additions & 0 deletions contracts/token/ERC721/extensions/draft-ERC721Votes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ abstract contract ERC721Votes is ERC721, Votes {
super._afterTokenTransfer(from, to, tokenId);
}

/**
* @dev Adjusts votes when a batch of tokens is transferred.
*
* Emits a {Votes-DelegateVotesChanged} event.
*/
function _afterConsecutiveTokenTransfer(
address from,
address to,
uint256 first,
uint256 last
) internal virtual override {
_transferVotingUnits(from, to, last - first + 1);
super._afterConsecutiveTokenTransfer(from, to, first, last);
}

/**
* @dev Returns the balance of `account`.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ contract ERC721PresetMinterPauserAutoId is
super._beforeTokenTransfer(from, to, tokenId);
}

function _beforeConsecutiveTokenTransfer(
address from,
address to,
uint256 first,
uint256 last
) internal virtual override(ERC721, ERC721Enumerable, ERC721Pausable) {
super._beforeConsecutiveTokenTransfer(from, to, first, last);
}

/**
* @dev See {IERC165-supportsInterface}.
*/
Expand Down
Loading

0 comments on commit 03835b3

Please sign in to comment.