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

Add ERC721ALowCap + tests #114

Merged
merged 24 commits into from
Apr 4, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions contracts/ERC721A.sol
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ contract ERC721A is Context, ERC165, IERC721, IERC721Metadata {
// Counter underflow is impossible as _burnCounter cannot be incremented
// more than _currentIndex times
unchecked {
return _currentIndex - _burnCounter;
return _currentIndex - _burnCounter;
}
}

Expand Down Expand Up @@ -175,8 +175,8 @@ contract ERC721A is Context, ERC165, IERC721, IERC721Metadata {
if (ownership.addr != address(0)) {
return ownership;
}
// Invariant:
// There will always be an ownership that has an address and is not burned
// Invariant:
// There will always be an ownership that has an address and is not burned
// before an ownership that does not have an address and is not burned.
// Hence, curr will not underflow.
while (true) {
Expand Down Expand Up @@ -493,7 +493,7 @@ contract ERC721A is Context, ERC165, IERC721, IERC721Metadata {
_afterTokenTransfers(prevOwnership.addr, address(0), tokenId, 1);

// Overflow not possible, as _burnCounter cannot be exceed _currentIndex times.
unchecked {
unchecked {
_burnCounter++;
}
}
Expand Down
52 changes: 52 additions & 0 deletions contracts/extensions/ERC721ALowCap.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT
// Creator: Chiru Labs

pragma solidity ^0.8.4;

import '../ERC721A.sol';

/**
* @title ERC721A Low Cap
* @dev ERC721A Helper functions for Low Cap (<= 10,000) totalSupply.
*/
abstract contract ERC721ALowCap is ERC721A {

/**
* @dev Returns the tokenIds of the address. O(totalSupply) in complexity.
*/
function tokensOfOwner(address owner) public view returns (uint256[] memory) {
uint256 holdingAmount = balanceOf(owner);
uint256 currSupply = _currentIndex;
Copy link
Contributor

@ahbanavi ahbanavi Feb 19, 2022

Choose a reason for hiding this comment

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

Remove extra white spaces before the assignment =. docs

uint256 tokenIdsIdx;
address currOwnershipAddr;

uint256[] memory list = new uint256[](holdingAmount);

unchecked {
for (uint256 i; i < currSupply; i++) {
TokenOwnership memory ownership = _ownerships[i];

if (ownership.burned) {
continue;
}

// Find out who owns this sequence
if (ownership.addr != address(0)) {
currOwnershipAddr = ownership.addr;
}

// Append tokens the last found owner owns in the sequence
if (currOwnershipAddr == owner) {
list[tokenIdsIdx++] = i;
}

// All tokens have been found, we don't need to keep searching
if(tokenIdsIdx == holdingAmount) {
break;
}
}
}

return list;
}
}
14 changes: 14 additions & 0 deletions contracts/mocks/ERC721ALowCap.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
// Creators: Chiru Labs

pragma solidity ^0.8.4;

import '../extensions/ERC721ALowCap.sol';

contract ERC721ALowCapMock is ERC721ALowCap {
Copy link
Contributor

@ahbanavi ahbanavi Feb 19, 2022

Choose a reason for hiding this comment

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

Filename should be the same as contract name, try changing the filename to ERC721ALowCapMock.sol. docs

constructor(string memory name_, string memory symbol_) ERC721A(name_, symbol_) {}

function safeMint(address to, uint256 quantity) public {
_safeMint(to, quantity);
}
}
2 changes: 1 addition & 1 deletion projects.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ Here are a list of projects that have or will be implementing ERC721A as part of
- Anim Ape | [Etherscan](https://etherscan.io/address/0xc4f44b646353b1a07053ebc939954f62d35c80b8) | [Twitter](https://twitter.com/Mysthereum_NFT)
- [Capsule Machine NFT](https://www.capsulemachinenft.com/) | [Etherscan](https://etherscan.io/address/0xc19ced6633f0da7cef642b7a3f6b3ff0bb2465c0) | [Twitter](https://twitter.com/_capsulemachine)
- [Mister Otter River Club](https://morc.vercel.app/) | [Etherscan](https://etherscan.io/address/0xa8c724a829a48f551950a783c6ec50e728725026) | [Twitter](https://twitter.com/misterotternft)
- [Tasty Bones NFT](https://tastybones.xyz/) | [Etherscan](https://etherscan.io/address/0x1b79c7832ed9358E024F9e46E9c8b6f56633691B) | [Twitter](https://twitter.com/tastybonesnft)
- [Tasty Bones NFT](https://tastybones.xyz/) | [Etherscan](https://etherscan.io/address/0x1b79c7832ed9358E024F9e46E9c8b6f56633691B) | [Twitter](https://twitter.com/tastybonesnft)
58 changes: 58 additions & 0 deletions test/extensions/ERC721ALowCap.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const { expect } = require('chai');

describe('ERC721ALowCap', function () {
beforeEach(async function () {
this.ERC721ALowCap = await ethers.getContractFactory('ERC721ALowCapMock');
this.token = await this.ERC721ALowCap.deploy('Azuki', 'AZUKI');
await this.token.deployed();
});

context('with minted tokens', async function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Use 2 spaces for indentation in this context instead of 4.

Copy link
Contributor

Choose a reason for hiding this comment

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

Should be able to run npm run lint to autofix all formatting issues!

beforeEach(async function () {
const [owner, addr1, addr2, addr3, addr4] = await ethers.getSigners();
this.owner = owner;
this.addr1 = addr1;
this.addr2 = addr2;
this.addr3 = addr3;
this.addr4 = addr4;
await this.token['safeMint(address,uint256)'](addr1.address, 1);
await this.token['safeMint(address,uint256)'](addr2.address, 2);
await this.token['safeMint(address,uint256)'](addr3.address, 3);
});

describe('tokensOfOwner', async function() {
it('returns the correct token ids', async function() {
const expected_results = [
// Address 1 -- Single token
{ owner: this.addr1, tokens : [0] },
// Address 3 -- Multiple tokens
{ owner: this.addr3, tokens : [3,4,5] },
// Address 4 -- No tokens
{ owner: this.addr4, tokens : [] }
];

for(const expected_result of expected_results) {
const bn_tokens = await this.token['tokensOfOwner(address)'](expected_result.owner.address);
expect(bn_tokens.map(bn => bn.toNumber())).to.eql(expected_result.tokens);
}
});

it('returns the correct token ids after a transfer interferes with the normal logic', async function() {
// Owner of 6,7,8
await this.token['safeMint(address,uint256)'](this.owner.address, 3);

// Break sequential order
await this.token['transferFrom(address,address,uint256)'](this.owner.address, this.addr4.address, 7);

// Load balances
const owner_bn_tokens = await this.token['tokensOfOwner(address)'](this.owner.address);
const addr4_bn_tokens = await this.token['tokensOfOwner(address)'](this.addr4.address);

// Verify the function can still read the correct token ids
expect(owner_bn_tokens.map(bn => bn.toNumber())).to.eql([6,8]);
expect(addr4_bn_tokens.map(bn => bn.toNumber())).to.eql([7]);
});
});
});

});