Skip to content

Commit

Permalink
Introduce ERC1155 totalSupply() and exists() functions (#2593)
Browse files Browse the repository at this point in the history
Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
  • Loading branch information
Amxx and frangio committed May 20, 2021
1 parent 5f50b9f commit 406c836
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 0 deletions.
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');
});
});
});
});

0 comments on commit 406c836

Please sign in to comment.