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

Fix #89: Implement burn() function with reduced maxSupply in ERC721Community contracts #103

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
88 changes: 88 additions & 0 deletions ERC721CommunityBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract ERC721CommunityBase is Context, Ownable, ERC721Enumerable, ERC721URIStorage {
using Address for address;

uint256 private _maxSupply;
uint256 private _totalMinted;

constructor (
string memory name_,
string memory symbol_,
string memory baseTokenURI_,
uint256 maxSupply_
) ERC721(name_, symbol_) {
_maxSupply = maxSupply_;
_setBaseURI(baseTokenURI_);
}

function maxSupply() public view virtual returns (uint256) {
return _maxSupply;
}

function totalMinted() public view virtual returns (uint256) {
return _totalMinted;
}

function burn(uint256 tokenId) public virtual {
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721CommunityBase: Caller is not owner nor approved");
_burn(tokenId);
_maxSupply -= 1;
}

function reduceMaxSupply(uint256 newMaxSupply) public onlyOwner {
require(newMaxSupply <= _maxSupply, "ERC721CommunityBase: New max supply is greater than current max supply");
require(newMaxSupply >= _totalMinted, "ERC721CommunityBase: New max supply is less than total minted");
_maxSupply = newMaxSupply;
}
require(newMaxSupply <= _maxSupply, "ERC721CommunityBase: New max supply is greater than current max supply");
require(newMaxSupply >= _totalMinted, "ERC721CommunityBase: New max supply is less than total minted");
_maxSupply = newMaxSupply;
}

// Overriden functions from OpenZeppelin's ERC721

function _mint(address to, uint256 tokenId) internal virtual override {
require(totalMinted() < maxSupply(), "ERC721CommunityBase: Max supply reached");
_totalMinted += 1;
super._mint(to, tokenId);
}

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

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

function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}

function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
56 changes: 56 additions & 0 deletions test/ERC721Community.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const {expect} = require("chai");
const {ethers} = require("hardhat");
const {utils} = require("ethers");

describe("ERC721Community - Burn function with OpenSea integration", function () {
let erc721Community;
let owner;
let addr1;
let addr2;
let addr3;
let tokenURI;
let tokenId;

beforeEach(async function () {
[owner, addr1, addr2, addr3] = await ethers.getSigners();
tokenURI = "https://example.com/token/";
tokenId = 1;

const ERC721Community = await ethers.getContractFactory("ERC721Community");
erc721Community = await ERC721Community.deploy("ERC721CommunityExample", "EXM");
await erc721Community.deployed();

await erc721Community.connect(addr1).mint(addr1.address, tokenId, tokenURI);
});

it("Should decrease maxSupply when a token is burned", async function () {
const initialMaxSupply = await erc721Community.maxSupply();
await erc721Community.connect(addr1).burn(tokenId);

const finalMaxSupply = await erc721Community.maxSupply();
expect(finalMaxSupply).to.equal(initialMaxSupply.sub(1));
});

it("Should decrease totalMinted and totalSupply when a token is burned", async function () {
const initialTotalMinted = await erc721Community.totalMinted();
const initialTotalSupply = await erc721Community.totalSupply();
await erc721Community.connect(addr1).burn(tokenId);

const finalTotalMinted = await erc721Community.totalMinted();
const finalTotalSupply = await erc721Community.totalSupply();
expect(finalTotalMinted).to.equal(initialTotalMinted.sub(1));
expect(finalTotalSupply).to.equal(initialTotalSupply.sub(1));
});

it("Should update token balance when a token is burned", async function () {
const initialBalance = await erc721Community.balanceOf(addr1.address);
await erc721Community.connect(addr1).burn(tokenId);

const finalBalance = await erc721Community.balanceOf(addr1.address);
expect(finalBalance).to.equal(initialBalance.sub(1));
});

it("Should fail if non-token owner tries to burn a token", async function () {
await expect(erc721Community.connect(addr2).burn(tokenId)).to.be.revertedWith("ERC721: burn caller is not owner nor approved");
});
});