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

Introduce ERC1155 totalSupply() and exists() functions #2593

Merged
merged 8 commits into from
May 20, 2021
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* `Counter`: add a reset method. ([#2678](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2678))
* Tokens: Wrap definitely safe subtractions in `unchecked` blocks.
* `Math`: Add a `ceilDiv` method for performing ceiling division.
* `ERC1155Supply`: add a new `ERC1155` extension that keeps track of the totalSupply of each tokenId. ([#2593](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2593))

### Breaking Changes

Expand Down
26 changes: 26 additions & 0 deletions contracts/mocks/ERC1155SupplyMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./ERC1155Mock.sol";
import "../token/ERC1155/extensions/ERC1155Supply.sol";

contract ERC1155SupplyMock is ERC1155Mock, ERC1155Supply {
constructor(string memory uri) ERC1155Mock(uri) { }

function _mint(address account, uint256 id, uint256 amount, bytes memory data) internal virtual override(ERC1155, ERC1155Supply) {
super._mint(account, id, amount, data);
}

function _mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) internal virtual override(ERC1155, ERC1155Supply) {
super._mintBatch(to, ids, amounts, data);
}

function _burn(address account, uint256 id, uint256 amount) internal virtual override(ERC1155, ERC1155Supply) {
super._burn(account, id, amount);
}

function _burnBatch(address account, uint256[] memory ids, uint256[] memory amounts) internal virtual override(ERC1155, ERC1155Supply) {
super._burnBatch(account, ids, amounts);
}
}
2 changes: 2 additions & 0 deletions contracts/token/ERC1155/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel

{{ERC1155Burnable}}

{{ERC1155Supply}}

== Presets

These contracts are preconfigured combinations of the above features. They can be used through inheritance or as models to copy and paste their source code.
Expand Down
67 changes: 67 additions & 0 deletions contracts/token/ERC1155/extensions/ERC1155Supply.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../ERC1155.sol";

/**
* @dev Extension of ERC1155 that adds tracking of total supply per id.
*
* Useful for scenarios where Fungible and Non-fungible tokens have to be
* clearly identified. Note: While a totalSupply of 1 might mean the
* corresponding is an NFT, there is no guarantees that no other token with the
* same id are not going to be minted.
*/
abstract contract ERC1155Supply is ERC1155 {
mapping (uint256 => uint256) private _totalSupply;

/**
* @dev Total amount of tokens in with a given id.
*/
function totalSupply(uint256 id) public view virtual returns (uint256) {
return _totalSupply[id];
}

/**
* @dev Indicates weither any token exist with a given id, or not.
*/
function exists(uint256 id) public view virtual returns(bool) {
return ERC1155Supply.totalSupply(id) > 0;
}

/**
* @dev See {ERC1155-_mint}.
*/
function _mint(address account, uint256 id, uint256 amount, bytes memory data) internal virtual override {
super._mint(account, id, amount, data);
_totalSupply[id] += amount;
}

/**
* @dev See {ERC1155-_mintBatch}.
*/
function _mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) internal virtual override {
super._mintBatch(to, ids, amounts, data);
for (uint256 i = 0; i < ids.length; ++i) {
_totalSupply[ids[i]] += amounts[i];
}
}

/**
* @dev See {ERC1155-_burn}.
*/
function _burn(address account, uint256 id, uint256 amount) internal virtual override {
super._burn(account, id, amount);
_totalSupply[id] -= amount;
}

/**
* @dev See {ERC1155-_burnBatch}.
*/
function _burnBatch(address account, uint256[] memory ids, uint256[] memory amounts) internal virtual override {
super._burnBatch(account, ids, amounts);
for (uint256 i = 0; i < ids.length; ++i) {
_totalSupply[ids[i]] -= amounts[i];
}
}
}
111 changes: 111 additions & 0 deletions test/token/ERC1155/extensions/ERC1155Supply.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const { BN } = require('@openzeppelin/test-helpers');

const { expect } = require('chai');

const ERC1155SupplyMock = artifacts.require('ERC1155SupplyMock');

contract('ERC1155Supply', function (accounts) {
const [ holder ] = accounts;

const uri = 'https://token.com';

const firstTokenId = new BN('37');
const firstTokenAmount = new BN('42');

const secondTokenId = new BN('19842');
const secondTokenAmount = new BN('23');

beforeEach(async function () {
this.token = await ERC1155SupplyMock.new(uri);
});

context('before mint', function () {
it('exist', async function () {
expect(await this.token.exists(firstTokenId)).to.be.equal(false);
});

it('totalSupply', async function () {
expect(await this.token.totalSupply(firstTokenId)).to.be.bignumber.equal('0');
});
});

context('after mint', function () {
context('single', function () {
beforeEach(async function () {
await this.token.mint(holder, firstTokenId, firstTokenAmount, '0x');
});

it('exist', async function () {
expect(await this.token.exists(firstTokenId)).to.be.equal(true);
});

it('totalSupply', async function () {
expect(await this.token.totalSupply(firstTokenId)).to.be.bignumber.equal(firstTokenAmount);
});
});

context('batch', function () {
beforeEach(async function () {
await this.token.mintBatch(
holder,
[ firstTokenId, secondTokenId ],
[ firstTokenAmount, secondTokenAmount ],
'0x',
);
});

it('exist', async function () {
expect(await this.token.exists(firstTokenId)).to.be.equal(true);
expect(await this.token.exists(secondTokenId)).to.be.equal(true);
});

it('totalSupply', async function () {
expect(await this.token.totalSupply(firstTokenId)).to.be.bignumber.equal(firstTokenAmount);
expect(await this.token.totalSupply(secondTokenId)).to.be.bignumber.equal(secondTokenAmount);
});
});
});

context('after burn', function () {
context('single', function () {
beforeEach(async function () {
await this.token.mint(holder, firstTokenId, firstTokenAmount, '0x');
await this.token.burn(holder, firstTokenId, firstTokenAmount);
});

it('exist', async function () {
expect(await this.token.exists(firstTokenId)).to.be.equal(false);
});

it('totalSupply', async function () {
expect(await this.token.totalSupply(firstTokenId)).to.be.bignumber.equal('0');
});
});

context('batch', function () {
beforeEach(async function () {
await this.token.mintBatch(
holder,
[ firstTokenId, secondTokenId ],
[ firstTokenAmount, secondTokenAmount ],
'0x',
);
await this.token.burnBatch(
holder,
[ firstTokenId, secondTokenId ],
[ firstTokenAmount, secondTokenAmount ],
);
});

it('exist', async function () {
expect(await this.token.exists(firstTokenId)).to.be.equal(false);
expect(await this.token.exists(secondTokenId)).to.be.equal(false);
});

it('totalSupply', async function () {
expect(await this.token.totalSupply(firstTokenId)).to.be.bignumber.equal('0');
expect(await this.token.totalSupply(secondTokenId)).to.be.bignumber.equal('0');
});
});
});
});