Skip to content

Commit

Permalink
Implement ERC721CommonAutoIncrement to provide zero-indexed tokenId i…
Browse files Browse the repository at this point in the history
…ncrementing + totalSupply().

The _safeMintN(address,uint,[bytes]) functions work identically to _safeMint() but the uint
specifies a number of tokens, not the next ID.
  • Loading branch information
ARR4N committed Apr 1, 2022
1 parent b6cb76f commit 056c7cd
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 0 deletions.
47 changes: 47 additions & 0 deletions contracts/erc721/ERC721CommonAutoIncrement.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 the ethier authors (github.com/divergencetech/ethier)
pragma solidity >=0.8.0 <0.9.0;

import "./ERC721Common.sol";
import "../utils/Monotonic.sol";

/**
@notice An extension of the ethier ERC721Common contract that auto-increments
the next tokenId and exposes a totalSupply.
*/
contract ERC721CommonAutoIncrement is ERC721Common {
using Monotonic for Monotonic.Increaser;

constructor(string memory name, string memory symbol)
ERC721Common(name, symbol)
{}

/**
@notice Total number of tokens minted.
*/
Monotonic.Increaser public totalSupply;

/**
@dev _safeMint() n tokens to the specified address, only incrementing
totalSupply once.
*/
function _safeMintN(
address to,
uint256 n,
bytes memory data
) internal {
uint256 tokenId = totalSupply.current();
uint256 end = tokenId + n;
for (; tokenId < end; ++tokenId) {
_safeMint(to, tokenId, data);
}
totalSupply.add(n);
}

/**
@dev Alias for _safeMintN(address,uint,bytes) assuming an empty byte buffer.
*/
function _safeMintN(address to, uint256 n) internal {
_safeMintN(to, n, "");
}
}
23 changes: 23 additions & 0 deletions tests/erc721/TestableERC721CommonEnumerable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity >=0.8.0 <0.9.0;

import "../../contracts/erc721/ERC721CommonEnumerable.sol";
import "../../contracts/erc721/ERC721AutoIncrement.sol";
import "../../contracts/erc721/BaseTokenURI.sol";

/// @notice Exposes a functions modified with the modifiers under test.
Expand Down Expand Up @@ -38,3 +39,25 @@ contract TestableERC721CommonEnumerable is
return BaseTokenURI._baseURI();
}
}

contract TestableERC721AutoIncrement is ERC721CommonAutoIncrement {
using Monotonic for Monotonic.Increaser;

constructor() ERC721CommonAutoIncrement("", "") {}

function safeMintN(address to, uint256 n) external {
_safeMintN(to, n);
}

/**
@dev Returns all token owners for testing.
*/
function allOwners() external view returns (address[] memory) {
uint256 n = totalSupply.current();
address[] memory owners = new address[](n);
for (uint256 i = 0; i < n; ++i) {
owners[i] = ownerOf(i);
}
return owners;
}
}
79 changes: 79 additions & 0 deletions tests/erc721/erc721_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
const (
deployer = iota
tokenOwner
tokenOwner2
tokenReceiver
approved
vandal
Expand Down Expand Up @@ -624,3 +625,81 @@ func TestBaseTokenURI(t *testing.T) {
wantURI(t, 42, "good/42")
wantURI(t, 101010, "good/101010")
}

func TestAutoIncrement(t *testing.T) {
sim := ethtest.NewSimulatedBackendTB(t, numAccounts)
openseatest.DeployProxyRegistryTB(t, sim)

_, _, nft, err := DeployTestableERC721AutoIncrement(sim.Acc(deployer), sim)
if err != nil {
t.Fatalf("DeployTestableERC721AutoIncrement() error %v", err)
}

const (
o1 = tokenOwner
o2 = tokenOwner2
)

tests := []struct {
toAccountIndex int
n int64
wantOwnerIndices []int
wantTotalSupply int64
}{
{
toAccountIndex: o1,
n: 2,
wantOwnerIndices: []int{o1, o1},
wantTotalSupply: 2,
},
{
toAccountIndex: o2,
n: 0,
wantOwnerIndices: []int{o1, o1},
wantTotalSupply: 2,
},
{
toAccountIndex: o2,
n: 1,
wantOwnerIndices: []int{o1, o1, o2},
wantTotalSupply: 3,
},
{
toAccountIndex: o2,
n: 3,
wantOwnerIndices: []int{o1, o1, o2, o2, o2, o2},
wantTotalSupply: 6,
},
{
toAccountIndex: o1,
n: 1,
wantOwnerIndices: []int{o1, o1, o2, o2, o2, o2, o1},
wantTotalSupply: 7,
},
}

for _, tt := range tests {
to := sim.Addr(tt.toAccountIndex)
sim.Must(t, "safeMintN(%v, %d)", to, tt.n)(nft.SafeMintN(sim.Acc(deployer), to, big.NewInt(tt.n)))

t.Run("owners", func(t *testing.T) {
gotOwners, err := nft.AllOwners(nil)
if err != nil {
t.Fatalf("AllOwners() error %v", err)
}

var wantOwners []common.Address
for _, i := range tt.wantOwnerIndices {
wantOwners = append(wantOwners, sim.Addr(i))
}

if diff := cmp.Diff(wantOwners, gotOwners); diff != "" {
t.Errorf("AllOwners() diff (-want +got):\n%s", diff)
}
})

if got, err := nft.TotalSupply(nil); err != nil || got.Cmp(big.NewInt(tt.wantTotalSupply)) != 0 {
t.Errorf("TotalSupply() got %d, err = %v; want %d, nil err", got, err, tt.wantTotalSupply)
}
}
}

0 comments on commit 056c7cd

Please sign in to comment.